diff --git a/modules/oms/home.scm b/modules/oms/home.scm index bb3c930..890f416 100644 --- a/modules/oms/home.scm +++ b/modules/oms/home.scm @@ -20,7 +20,9 @@ #:use-module (gnu packages version-control) #:use-module (gnu packages video) #:use-module (guix gexp) - #:use-module (oms channel-file)) + #:use-module (oms channel-file) + #:use-module (oms home gsettings-schemas) + #:use-module (oms home services gnome)) (define-public oms-home-environment (home-environment @@ -49,7 +51,11 @@ yt-dlp)) (services (append - (list (service home-zsh-service-type) + (list (service home-gsettings-service-type + (home-gsettings-configuration + (reset? #t) + (schemas oms-gsettings-schemas))) + (service home-zsh-service-type) (service home-xdg-user-directories-service-type (home-xdg-user-directories-configuration (desktop "$HOME") diff --git a/modules/oms/home/gsettings-schemas.scm b/modules/oms/home/gsettings-schemas.scm new file mode 100644 index 0000000..6614e4f --- /dev/null +++ b/modules/oms/home/gsettings-schemas.scm @@ -0,0 +1,98 @@ +(define-module (oms home gsettings-schemas) + #:use-module (oms home services gnome) + #:export (oms-gsettings-schemas)) + +(define oms-gsettings-schemas + (list + (gsettings-schema + (name "org.gnome.desktop.screen-time-limits") + (settings '(("history-enabled" . #f)))) + (gsettings-schema + (name "org.gnome.mutter") + (settings '(("edge-tiling" . #t) + ("dynamic-workspaces" . #t)))) + (gsettings-schema + (name "org.gnome.desktop.interface") + (settings '(("enable-hot-corners" . #t)))) + (gsettings-schema + (name "org.gnome.desktop.calendar") + (settings '(("show-weekdate" . #t)))) + (gsettings-schema + (name "org.gnome.settings-daemon.plugins.color") + (settings `(("night-light-enabled" . #t) + ("night-light-schedule-automatic" . #t) + ("night-light-last-coordinates" . + ,(raw-gvariant "(60.0, 25.0)"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders") + (settings '(("folder-children" . + ("System" "Utilities" "Graphics" "Games" "Office"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Games/") + (settings '(("name" . "Game.directory") + ("translate" . #t) + ("apps" . + ("org.luanti.luanti.desktop" + "org.wesnoth.Wesnoth.desktop" + "xonotic.desktop" + "supertuxkart.desktop" + "0ad.desktop" + "org.gnome.TwentyFortyEight.desktop" + "org.gnome.Chess.desktop" + "xonotic-sdl.desktop" + "xonotic-glx.desktop"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Graphics/") + (settings '(("name" . "Graphics.directory") + ("translate" . #t) + ("apps" . + ("gimp.desktop" + "blender.desktop" + "org.inkscape.Inkscape.desktop" + "org.kde.krita.desktop"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Office/") + (settings '(("name" . "Office.directory") + ("translate" . #t) + ("apps" . + ("libreoffice-calc.desktop" + "libreoffice-writer.desktop" + "libreoffice-impress.desktop" + "libreoffice-draw.desktop" + "libreoffice-math.desktop" + "libreoffice-base.desktop" + "libreoffice-startcenter.desktop" + "org.gnome.gitlab.somas.Apostrophe.desktop" + "com.github.xournalpp.xournalpp.desktop" + "org.gnome.Papers.desktop"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Utilities/") + (settings '(("name" . "X-GNOME-Shell-Utilities.directory") + ("translate" . #t) + ("apps" . + ("org.gnome.Decibels.desktop" + "org.gnome.Connections.desktop" + "org.gnome.Evince.desktop" + "org.gnome.FileRoller.desktop" + "org.gnome.font-viewer.desktop" + "org.gnome.Loupe.desktop" + "org.gnome.Tour.desktop" + "gucharmap.desktop" + "xdvi.desktop"))))) + (gsettings-schema + (name "org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/System/") + (settings '(("name" . "X-GNOME-Shell-System.directory") + ("translate" . #t) + ("apps" . + ("nm-connection-editor.desktop" + "org.gnome.baobab.desktop" + "org.gnome.DiskUtility.desktop" + "org.gnome.Sysprof.desktop" + "org.gnome.SystemMonitor.desktop" + "org.gnome.Extensions.desktop" + "org.gnome.Settings.desktop" + "system-config-printer.desktop" + "org.gnome.Console.desktop"))))) + (gsettings-schema + (name "org.gnome.shell") + (settings `(("app-picker-layout" . ,(raw-gvariant "[{'org.gnome.Contacts.desktop': <{'position': <0>}>, 'org.gnome.Weather.desktop': <{'position': <1>}>, 'org.gnome.clocks.desktop': <{'position': <2>}>, 'org.gnome.Music.desktop': <{'position': <3>}>, 'org.gnome.Snapshot.desktop': <{'position': <4>}>, 'yelp.desktop': <{'position': <5>}>, 'org.gnome.Characters.desktop': <{'position': <6>}>, 'org.gnome.Showtime.desktop': <{'position': <7>}>, 'org.gnome.SimpleScan.desktop': <{'position': <8>}>, 'org.gnome.Screenshot.desktop': <{'position': <9>}>, 'qalculate-gtk.desktop': <{'position': <10>}>, 'Graphics': <{'position': <11>}>, 'Office': <{'position': <12>}>, 'Games': <{'position': <13>}>, 'Utilities': <{'position': <14>}>, 'System': <{'position': <15>}>}]"))))))) diff --git a/modules/oms/home/services/gnome.scm b/modules/oms/home/services/gnome.scm new file mode 100644 index 0000000..eefcdd7 --- /dev/null +++ b/modules/oms/home/services/gnome.scm @@ -0,0 +1,187 @@ +(define-module (oms home services gnome) + #:use-module (gnu home services) + #:use-module (gnu home services shepherd) + #:use-module (gnu packages glib) + #:use-module (gnu packages gnome) + #:use-module (gnu services configuration) + #:use-module ((guix diagnostics) #:select (warning)) + #:use-module (guix gexp) + #:use-module (guix i18n) + #:use-module (ice-9 match) + #:use-module (ice-9 string-fun) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9 gnu) + #:use-module (srfi srfi-11) + #:use-module (srfi srfi-26) + #:export (home-gsettings-configuration + home-gsettings-configuration? + home-gsettings-configuration-reset? + home-gsettings-configuration-schemas + + gsettings-schema + gsettings-schema? + gsettings-schema-name + gsettings-schema-settings + + raw-gvariant + raw-gvariant? + raw-gvariant-value + + home-gsettings-service-type)) + +(define-immutable-record-type + (raw-gvariant value) + raw-gvariant? + (value raw-gvariant-value)) + +(define (gvariant-association-list? x) + (match x + ((((? string?) . (? gvariant-printable?)) ...) #t) + (_ #f))) + +(define (list-of-gsettings-schemas? x) + (and (list? x) + ;; TODO who does this not work??? + ;(every (lambda (x) ((gsettings-schema? x))) x) + )) + +(define-configuration/no-serialization home-gsettings-configuration + (reset? + (boolean #t) + "Whether to reset all undeclared GSettings to default values. Resetting +ensures the configuration is declarative but makes it impossible to modify +settings persistently from any application's user interface.") + (schemas + (list-of-gsettings-schemas '()) + "The declared GSettings configuration values.")) + +(define-configuration/no-serialization gsettings-schema + (name + string + "The identifier of the GSettings schema. For example \"org.gnome.desktop.background\".") + (settings + (gvariant-association-list) + "Keys and values to set in this schema.")) + +(define* (group lst #:key (similar? equal?)) + "Returns a list of non-empty lists such that all the values in each sublist +are SIMILAR? to it's first one." + (match lst + ((head _ ...) + (let-values (((similar different) + (partition (cut similar? head <>) lst))) + (cons similar (group different #:similar? similar?)))) + (() '()))) + +(define (equal?-under view) + (lambda (x y) + (equal? (view x) + (view y)))) + +(define (gvariant-printable? x) + (or (boolean? x) + (string? x) + (file-like? x) + (number? x) + (and (list? x) + (every gvariant-printable? x)) + (raw-gvariant? x))) + +(define (serialize-gvariant value) + "Serialize VALUE to a string in GVariant text format or to a G-expression +that expands to an expression that evaluates to such a string." + (match value + ((or (? string?) + (? file-like?)) + #~(begin + (use-modules (ice-9 string-fun)) + (format #f "'~a'" + (string-replace-substring + (string-replace-substring #$value "\\" "\\\\") + "'" "\\'")))) + ((? boolean?) + (if value "true" "false")) + ((? number?) + (format #f "~a" value)) + ((? raw-gvariant? (= raw-gvariant-value raw-value)) + raw-value) + (((? gvariant-printable? lst) ...) + #~(format #f "[~a]" (string-join (list #$@(map serialize-gvariant lst)) ", "))))) + +(define (home-gsettings-configuration->setting-list config) + "Convert a home-gsettings-configuration record into a list-based format with +serialized values. If the same key is set multiple times in the same schema, a +warning is raised and the leftmost value used." + (define (serialize-keyvals keyvals) + (match keyvals + (((key . value)) + #~(list #$key #$(serialize-gvariant value))) + (((key . value) (_ . ignored) ...) + (warning (G_ "Multiple values set for GSetting key '~a ~a'! \ +Using '~a' and ignoring the rest ~a.") + schema-name key value ignored) + #~(list #$key #$(serialize-gvariant value))))) + (define (merge-schemas schemas) + (let ((schema-name (gsettings-schema-name (car schemas)))) + #~(list #$schema-name + #$@(map serialize-keyvals + (group (append-map gsettings-schema-settings schemas) + #:similar? (equal?-under car)))))) + #~(list + #$@(map merge-schemas + (group (home-gsettings-configuration-schemas config) + #:similar? (equal?-under gsettings-schema-name))))) + +(define (extend-gsettings config extension-schemas) + (home-gsettings-configuration + (inherit config) + (schemas + ;; Leftmost values are preferred so instantiation overrides extensions + (append (home-gsettings-configuration-schemas config) + extension-schemas)))) + +(define (make-gsettings-script config) + (program-file + "set-gsettings" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils) + (ice-9 match)) + ;; Pass options via the environment to allow reusing this script + (when #$(home-gsettings-configuration-reset? config) + (invoke #$(file-append dconf "/bin/dconf") "reset" "-f" "/")) + (match #$(home-gsettings-configuration->setting-list config) + ((((? string? schemas) keyvals ...) ...) + (map (lambda (schema keyvals) + (map (match-lambda + ((key value) + (invoke #$(file-append (gexp-input glib "bin") + "/bin/gsettings") + "set" schema key value))) + keyvals)) + schemas keyvals))))))) + +(define (home-gsettings-shepherd-services config) + (list + (shepherd-service + (documentation "Sets declared GSettings configuration values.") + (provision '(gsettings)) + (one-shot? #t) + (start #~(make-forkexec-constructor + (list #$(make-gsettings-script config))))))) + +(define home-gsettings-service-type + (service-type + (name 'home-gsettings) + (extensions + (list (service-extension home-shepherd-service-type + home-gsettings-shepherd-services))) + (compose concatenate) + (extend extend-gsettings) + (default-value + (home-gsettings-configuration + ;; If the user has not added the service to their home configuration + ;; themselves, they probably don't want it to reset their settings when + ;; another service extends it. + (reset? #f))) + (description "Declare values for GSettings configuration settings used by GNOME.")))