;;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Copyright © 2025 Hilton Chain <hako@ultrarare.space>

(define-module (rosenthal services desktop)
  ;; Utilities
  #:use-module (guix deprecation)
  #:use-module (guix diagnostics)
  #:use-module (guix gexp)
  #:use-module (guix i18n)
  #:use-module (guix modules)
  #:use-module (guix packages)
  #:use-module (guix records)
  #:use-module (guix utils)
  #:use-module (rosenthal utils file)
  #:use-module (rosenthal utils packages)
  ;; Guix build systems
  #:use-module (guix build-system copy)
  ;; Guix System
  #:use-module (gnu system)
  #:use-module (gnu system shadow)
  ;; Guix System - services
  #:use-module (gnu services)
  #:use-module (gnu services base)
  #:use-module (gnu services configuration)
  #:use-module (gnu services desktop)
  #:use-module (gnu services pm)
  #:use-module (gnu services sddm)
  #:use-module (gnu services xorg)
  #:use-module (rosenthal services base)
  #:use-module (rosenthal services networking)
  ;; Guix Home - services
  #:use-module (gnu home)
  #:use-module (gnu home services)
  #:use-module (gnu home services desktop)
  #:use-module (gnu home services shepherd)
  #:use-module (gnu home services sound)
  #:use-module (rosenthal home services desktop)
  #:use-module (rosenthal home services gtk)
  ;; Guix packages
  #:use-module (gnu packages fcitx5)
  #:use-module (gnu packages fonts)
  #:use-module (gnu packages gnome)
  #:use-module (gnu packages guile)
  #:use-module (gnu packages hardware)
  #:use-module (gnu packages linux)
  #:use-module (gnu packages networking)
  #:use-module (gnu packages polkit)
  #:use-module (gnu packages qt)
  #:use-module (gnu packages window-management)
  #:use-module (gnu packages xorg)
  #:use-module (rosenthal packages authentication)
  #:use-module (rosenthal packages wm)
  #:export (home-blueman-applet-configuration
            home-blueman-applet-service-type

            home-fcitx5-configuration
            home-fcitx5-service-type

            home-mako-configuration
            home-mako-service-type

            home-network-manager-applet-configuration
            home-network-manager-applet-service-type

            home-niri-configuration
            home-niri-service-type

            home-bb-auth-service-type
            home-polkit-gnome-service-type

            home-rofi-configuration
            home-rofi-service-type

            home-swaybg-configuration
            home-swaybg-service-type

            home-theme-configuration
            home-theme-service-type

            home-waybar-configuration
            home-waybar-service-type

            %rosenthal-skeletons
            %rosenthal-skeletons-installer
            %rosenthal-desktop-services/base
            %rosenthal-desktop-services/gdm
            %rosenthal-desktop-services/tuigreet

            %rosenthal-desktop-services-gdm ;deprecated
            %rosenthal-desktop-services     ;deprecated

            %rosenthal-desktop-home-services))


;;;
;;; Blueman
;;;

(define-configuration/no-serialization home-blueman-applet-configuration
  (blueman
   (file-like blueman)
   ""))

(define %home-blueman-applet-shepherd
  (match-record-lambda <home-blueman-applet-configuration>
      (blueman)
    (list (shepherd-service
            (documentation "Start blueman applet.")
            (provision '(blueman-applet))
            (requirement '(graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append blueman "/bin/blueman-applet"))
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-blueman-applet-service-type
  (service-type
    (name 'home-blueman-applet)
    (extensions
     (list (service-extension home-profile-service-type
                              (compose list home-blueman-applet-configuration-blueman))
           (service-extension home-shepherd-service-type
                              %home-blueman-applet-shepherd)))
    (default-value (home-blueman-applet-configuration))
    (description "Run blueman applet, a tray applet for managing bluetooth.")))


;;;
;;; Fcitx5
;;;

(define list-of-file-likes?
  (list-of file-like?))
(define-maybe file-like)
(define-maybe boolean)

(define-configuration/no-serialization home-fcitx5-configuration
  (fcitx5
   (file-like fcitx5)
   "")
  (utilities
   (list-of-file-likes (list fcitx5-configtool))
   "")
  (themes
   (list-of-file-likes '())
   "")
  (input-method-editors
   (list-of-file-likes '())
   "")
  (wayland-frontend?
   (boolean #f)
   "")
  ;; deprecated
  (gtk-im-module?
   maybe-boolean
   ""
   (sanitizer
    (lambda (config)
      (if (maybe-value-set? config)
          (warning
           (G_ "'~a': option '~a' is deprecated, please use '~a' instead.~%")
           "home-fcitx5-configuration"
           "gtk-im-module?"
           "wayland-frontend?")))))
  (qt-im-module?
   maybe-boolean
   ""
   (sanitizer
    (lambda (config)
      (if (maybe-value-set? config)
          (warning
           (G_ "'~a': option '~a' is deprecated, please use '~a' instead.~%")
           "home-fcitx5-configuration"
           "qt-im-module?"
           "wayland-frontend?")))))
  (xim?
   maybe-boolean
   ""
   (sanitizer
    (lambda (config)
      (if (maybe-value-set? config)
          (warning
           (G_ "'~a': option '~a' is deprecated.~%")
           "home-fcitx5-configuration"
           "xim?"))))))

;; https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland
(define home-fcitx5-environment-variables-service
  (match-record-lambda <home-fcitx5-configuration>
      (wayland-frontend?)
    `(("XMODIFIERS" . "@im=fcitx")
      ("QT_IM_MODULE" . "fcitx")
      ,@(if wayland-frontend?
            '(("QT_IM_MODULES" . "wayland;fcitx"))
            '(("GTK_IM_MODULE" . "fcitx"))))))

(define home-fcitx5-gtk2-service
  (match-record-lambda <home-fcitx5-configuration>
      (wayland-frontend?)
    (if wayland-frontend?
        `(("gtk-im-module" . ,(format #f "~s" "fcitx")))
        '())))

(define home-fcitx5-gtk3-service
  (match-record-lambda <home-fcitx5-configuration>
      (wayland-frontend?)
    (if wayland-frontend?
        `(("gtk-im-module" . "fcitx"))
        '())))

(define home-fcitx5-profile-service
  (match-record-lambda <home-fcitx5-configuration>
      (fcitx5 utilities themes input-method-editors wayland-frontend?)
    (append (list fcitx5 fcitx5-gtk fcitx5-qt)
            (if wayland-frontend?
                '()
                (list `(,fcitx5-gtk "gtk2")
                      `(,fcitx5-gtk "gtk3")
                      fcitx5-gtk4))
            utilities
            themes
            input-method-editors)))

(define home-fcitx5-shepherd-service
  (match-record-lambda <home-fcitx5-configuration>
      (fcitx5)
    (list (shepherd-service
            (documentation "Start fcitx5.")
            (provision '(fcitx5))
            (requirement '(dbus graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append fcitx5 "/bin/fcitx5"))
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-fcitx5-service-type
  (service-type
    (name 'home-fcitx5)
    (extensions
     (list (service-extension home-environment-variables-service-type
                              home-fcitx5-environment-variables-service)
           (service-extension home-gtk2-service-type
                              home-fcitx5-gtk2-service)
           (service-extension home-gtk3-service-type
                              home-fcitx5-gtk3-service)
           (service-extension home-gtk4-service-type
                              home-fcitx5-gtk3-service)
           (service-extension home-profile-service-type
                              home-fcitx5-profile-service)
           (service-extension home-shepherd-service-type
                              home-fcitx5-shepherd-service)))
    (default-value (home-fcitx5-configuration))
    (description "Run fcitx5, an input method framework.")))


;;;
;;; mako
;;;

(define-configuration/no-serialization home-mako-configuration
  (mako
   (file-like mako)
   "")
  (config
   maybe-file-like
   "")
  )

(define %home-mako-xdg-config
  (match-record-lambda <home-mako-configuration>
      (config)
    (if (maybe-value-set? config)
        `(("mako/config" ,config))
        '())))

(define %home-mako-shepherd
  (match-record-lambda <home-mako-configuration>
      (mako)
    (list (shepherd-service
            (documentation "Start mako.")
            (provision '(mako))
            (requirement '(graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append mako "/bin/mako"))
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-mako-service-type
  (service-type
    (name 'home-mako)
    (extensions
     (list (service-extension home-xdg-configuration-files-service-type
                              %home-mako-xdg-config)
           (service-extension home-shepherd-service-type
                              %home-mako-shepherd)
           (service-extension home-graphical-session-service-type
                              (const 'wayland))))
    (default-value (home-mako-configuration))
    (description "Run mako, a notification daemon.")))


;;;
;;; network-manager-applet
;;;

(define-configuration/no-serialization home-network-manager-applet-configuration
  (network-manager-applet
   (file-like network-manager-applet)
   ""))

(define %home-network-manager-applet-shepherd
  (match-record-lambda <home-network-manager-applet-configuration>
      (network-manager-applet)
    (list (shepherd-service
            (documentation "Start network manager applet.")
            (provision '(network-manager-applet))
            (requirement '(graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append network-manager-applet "/bin/nm-applet"))
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-network-manager-applet-service-type
  (service-type
    (name 'home-network-manager-applet)
    (extensions
     (list (service-extension home-profile-service-type
                              (compose list home-network-manager-applet-configuration-network-manager-applet))
           (service-extension home-shepherd-service-type
                              %home-network-manager-applet-shepherd)))
    (default-value (home-network-manager-applet-configuration))
    (description "Run nm-applet, a tray applet for managing networks.")))


;;;
;;; niri
;;;

(define-configuration/no-serialization home-niri-configuration
  (config
   maybe-file-like
   ""))

(define %home-niri-xdg-config
  (match-record-lambda <home-niri-configuration>
      (config)
    (if (maybe-value-set? config)
        `(("niri/config.kdl" ,config))
        '())))

(define home-niri-service-type
  (service-type
    (name 'home-niri)
    (extensions
     (list (service-extension home-xdg-configuration-files-service-type
                              %home-niri-xdg-config)))
    (default-value (home-niri-configuration))
    (description
     "Set up configuration file for niri, a scrollable-tiling Wayland
compositor.")))


;;;
;;; Noctalia
;;;

(define-deprecated/public-alias home-noctalia-shell-configuration
  (@ (rosenthal home services desktop) home-noctalia-configuration))

(define-deprecated/public-alias home-noctalia-shell-service-type
  (@ (rosenthal home services desktop) home-noctalia-service-type))


;;;
;;; bb-auth
;;;

(define (%home-bb-auth-shepherd _)
  (list (shepherd-service
          (provision '(bb-auth))
          (requirement '(graphical-session))
          (start
           #~(lambda args
               ((make-forkexec-constructor
                 (list #$(file-append bb-auth "/libexec/bb-auth") "--daemon")
                 ;; Inherit graphical session environment.
                 #:environment-variables (environ))
                args)))
          (stop #~(make-kill-destructor)))))

(define home-bb-auth-service-type
  (service-type
    (name 'home-bb-auth)
    (extensions
     (list (service-extension home-shepherd-service-type
                              %home-bb-auth-shepherd)))
    (default-value #f)
    (description "Run @command{bb-auth} daemon.")))


;;;
;;; polkit-gnome
;;;

(define (%home-polkit-gnome-shepherd _)
  (list (shepherd-service
          (provision '(polkit-gnome))
          (requirement '(graphical-session))
          (start
           #~(lambda args
               ((make-forkexec-constructor
                 (list #$(file-append polkit-gnome "/libexec/polkit-gnome-authentication-agent-1"))
                 ;; Inherit graphical session environment.
                 #:environment-variables (environ))
                args)))
          (stop #~(make-kill-destructor)))))

(define home-polkit-gnome-service-type
  (service-type
    (name 'home-polkit-gnome)
    (extensions
     (list (service-extension home-shepherd-service-type
                              %home-polkit-gnome-shepherd)))
    (default-value #f)
    (description "")))


;;;
;;; rofi
;;;

(define-configuration/no-serialization home-rofi-configuration
  (config
   maybe-file-like
   ""))

(define %home-rofi-xdg-config
  (match-record-lambda <home-rofi-configuration>
      (config)
    (if (maybe-value-set? config)
        `(("rofi/config.rasi" ,config))
        '())))

(define home-rofi-service-type
  (service-type
    (name 'home-rofi)
    (extensions
     (list (service-extension home-xdg-configuration-files-service-type
                              %home-rofi-xdg-config)))
    (default-value (home-rofi-configuration))
    (description
     "Set up configuration file for rofi, an application launcher.")))


;;;
;;; swaybg
;;;

(define-configuration/no-serialization home-swaybg-configuration
  (swaybg
   (file-like swaybg)
   "")
  (background
   (file-like (local-file "../examples/wallpaper.jpg"))
   ""))

(define %home-swaybg-shepherd
  (match-record-lambda <home-swaybg-configuration>
      (swaybg background)
    (list (shepherd-service
            (documentation "Start swaybg.")
            (provision '(swaybg))
            (requirement '(graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append swaybg "/bin/swaybg") "--mode" "fill"
                         "--image" #$background)
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-swaybg-service-type
  (service-type
    (name 'home-swaybg)
    (extensions
     (list (service-extension home-shepherd-service-type
                              %home-swaybg-shepherd)
           (service-extension home-graphical-session-service-type
                              (const 'wayland))))
    (default-value (home-swaybg-configuration))
    (description
     "Run swaybg, a screen wallpaper utility for Wayland compositors.")))


;;;
;;; theme
;;;

(define-configuration/no-serialization home-theme-configuration
  (packages
   (list-of-file-likes '())
   "")
  (icon-theme
   (string "Adwaita")
   "")
  (font
   (string "Sans")
   "")
  (cursor-theme
   (string "Adwaita")
   "")
  (cursor-size
   (number 24)
   "")
  (key-theme
   (string "Default")
   ""))

(define (%home-theme-environment-variables _)
  '(("QT_QPA_PLATFORMTHEME" . "gtk3")
    ("QT_WAYLAND_DECORATION" . "adwaita")))

(define %home-theme-profile
  (match-record-lambda <home-theme-configuration>
      (packages)
    (cons* adwaita-icon-theme
           hicolor-icon-theme
           packages)))

(define home-theme-icons-config
  (match-record-lambda <home-theme-configuration>
      (icon-theme)
    (ini-file "index.theme"
      #~'(("icon theme"
           . (("Inherits" . #$icon-theme)))))))

(define (home-theme-files config)
  `((".icons/default/index.theme"
     ,(home-theme-icons-config config))))

(define (home-theme-xdg-data-files config)
  `(("icons/default/index.theme"
     ,(home-theme-icons-config config))))

(define home-theme-gtk2
  (match-record-lambda <home-theme-configuration>
      (icon-theme font cursor-theme cursor-size key-theme)
    `(("gtk-theme-name"        . ,(format #f "~s" "Adwaita"))
      ("gtk-icon-theme-name"   . ,(format #f "~s" icon-theme))
      ("gtk-font-name"         . ,(format #f "~s" font))
      ("gtk-cursor-theme-name" . ,(format #f "~s" cursor-theme))
      ("gtk-cursor-theme-size" . ,cursor-size)
      ("gtk-key-theme-name"    . ,(format #f "~s" key-theme)))))

(define home-theme-gtk3
  (match-record-lambda <home-theme-configuration>
      (icon-theme font cursor-theme cursor-size key-theme)
    `(("gtk-theme-name"        . "Adwaita")
      ("gtk-icon-theme-name"   . ,icon-theme)
      ("gtk-font-name"         . ,font)
      ("gtk-cursor-theme-name" . ,cursor-theme)
      ("gtk-cursor-theme-size" . ,cursor-size)
      ("gtk-key-theme-name"    . ,key-theme))))

(define home-theme-service-type
  (service-type
    (name 'home-theme)
    (extensions
     (list (service-extension home-environment-variables-service-type
                              %home-theme-environment-variables)
           (service-extension home-profile-service-type
                              %home-theme-profile)
           (service-extension home-files-service-type
                              home-theme-files)
           (service-extension home-xdg-data-files-service-type
                              home-theme-xdg-data-files)
           (service-extension home-gtk2-service-type
                              home-theme-gtk2)
           (service-extension home-gtk3-service-type
                              home-theme-gtk3)
           (service-extension home-gtk4-service-type
                              home-theme-gtk3)))
    (default-value (home-theme-configuration))
    (description "Set up desktop themes.")))


;;;
;;; waybar
;;;

(define-configuration/no-serialization home-waybar-configuration
  (waybar
   (file-like waybar)
   "")
  (config
   maybe-file-like
   "")
  (style
   maybe-file-like
   ""))

(define %home-waybar-xdg-config
  (match-record-lambda <home-waybar-configuration>
      (config style)
    `(,@(if (maybe-value-set? config)
            `(("waybar/config.jsonc" ,config))
            '())
      ,@(if (maybe-value-set? style)
            `(("waybar/style.css" ,style))
            '()))))

(define %home-waybar-shepherd
  (match-record-lambda <home-waybar-configuration>
      (waybar)
    (list (shepherd-service
            (documentation "Start waybar.")
            (provision '(waybar))
            (requirement '(graphical-session))
            (start
             #~(lambda args
                 ((make-forkexec-constructor
                   (list #$(file-append waybar "/bin/waybar"))
                   ;; Inherit graphical session environment.
                   #:environment-variables (environ))
                  args)))
            (stop #~(make-kill-destructor))))))

(define home-waybar-service-type
  (service-type
    (name 'home-waybar)
    (extensions
     (list (service-extension home-xdg-configuration-files-service-type
                              %home-waybar-xdg-config)
           (service-extension home-shepherd-service-type
                              %home-waybar-shepherd)
           (service-extension home-graphical-session-service-type
                              (const 'wayland))))
    (default-value (home-waybar-configuration))
    (description "Run waybar, a status bar for Wayland compositors.")))


;;;
;;; Configuration file presets.
;;;

(define %rosenthal-skeletons
  `((".config/emacs/init.el"
     ,(local-file "../examples/emacs/init.el"))
    (".config/fcitx5/config"
     ,(local-file "../examples/dot-config/fcitx5/config"))
    (".config/htop/htoprc"
     ,(local-file "../examples/dot-config/htop/htoprc"))
    (".config/niri/config.kdl"
     ,(local-file "../examples/dot-config/niri/config.kdl"))
    (".config/xfce4/helpers.rc"
     ,(local-file "../examples/dot-config/xfce4/helpers.rc"))

    ,@(default-skeletons)))

(define %rosenthal-skeletons-installer
  `((".config/noctalia/settings.json"
     ,(local-file "../examples/dot-config/noctalia/settings.json"))
    (".config/wezterm/wezterm.lua"
     ,(local-file "../examples/dot-config/wezterm/wezterm.lua"))
    ;; Prevent Noctalia shell initial screen.
    (".cache/noctalia/shell-state.json"
     ,(json-file "noctalia-shell-state.json"
        #~'(("changelogState"
             ("lastSeenVersion"
              . #$(string-append "v" (package-version noctalia-shell))))
            ("telemetry"
             ("instanceId" . "")))))
    ,@%rosenthal-skeletons))


;;;
;;; Service presets.
;;;

(define* (base-rosenthal-desktop-services
          #:optional (system (or (%current-target-system)
                                 (%current-system))))

  (define %display-manager-service-type
    (if (target-64bit? system)
        gdm-service-type
        sddm-service-type))

  (cons* (service bluetooth-service-type
           (bluetooth-configuration
             (auto-enable? #t)))

         (service gvfs-service-type)

         (service power-profiles-daemon-service-type)

         ;; Screen lockers for Wayland environment.  No dependencies are pulled
         ;; in since we're using empty files.
         (service screen-locker-service-type
           (screen-locker-configuration
             (name "swaylock")
             (program (plain-file "empty" "")) ;Not used.
             (using-setuid? #f)))
         (service screen-locker-service-type
           (screen-locker-configuration
             (name "waylock")
             (program (plain-file "empty" "")) ;Not used.
             (using-setuid? #f)))

         ;; Add udev rules for backlight control.
         (simple-service 'backlight udev-service-type (list ddcutil light))

         (modify-services %desktop-services
           (delete %display-manager-service-type)
           ;; Use a font suitable for HiDPI monitors.
           (console-font-service-type
            _ => (map (lambda (num)
                        (cons (string-append "tty" (number->string num))
                              (file-append font-terminus
                                           "/share/consolefonts/ter-132n")))
                      (iota 6 1))))))

(define-syntax %rosenthal-desktop-services/base
  (identifier-syntax (base-rosenthal-desktop-services)))

(define %rosenthal-desktop-services/gdm
  (cons* (service gdm-service-type)
         %rosenthal-desktop-services/base))

(define %rosenthal-desktop-services/tuigreet
  (cons* (service greetd-service-type
           (greetd-configuration
             (greeter-supplementary-groups '("video" "input"))
             (terminals
              (map (lambda (x)
                     (greetd-terminal-configuration
                       (terminal-vt (number->string x))
                       (terminal-switch (eqv? 1 x))
                       (default-session-command
                         (cond
                          ((eqv? 1 x)
                           (greetd-tuigreet-session))
                          (else
                           (greetd-agreety-session
                             (command
                              (greetd-user-session
                                (command #~(getenv "SHELL"))))))))))
                   (iota 6 1)))))
         (modify-services %rosenthal-desktop-services/base
           (delete mingetty-service-type))))

(define-deprecated %rosenthal-desktop-services-gdm %rosenthal-desktop-services/gdm)
(define-deprecated %rosenthal-desktop-services %rosenthal-desktop-services/tuigreet)

(define %rosenthal-desktop-home-services
  (cons* (service home-dbus-service-type)
         (service home-pipewire-service-type)
         %base-home-services))
