xdg.scm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021, 2022 Andrew Tropin <andrew@trop.in>
  3. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (gnu home services xdg)
  20. #:use-module (gnu services configuration)
  21. #:use-module (gnu home services)
  22. #:use-module (gnu packages freedesktop)
  23. #:use-module (gnu home services utils)
  24. #:use-module (guix gexp)
  25. #:use-module (guix modules)
  26. #:use-module (guix records)
  27. #:use-module (guix i18n)
  28. #:use-module (guix diagnostics)
  29. #:use-module (ice-9 match)
  30. #:use-module (srfi srfi-1)
  31. #:use-module (rnrs enums)
  32. #:export (home-xdg-base-directories-service-type
  33. home-xdg-base-directories-configuration
  34. home-xdg-base-directories-configuration?
  35. home-xdg-base-directories-configuration-cache-home
  36. home-xdg-base-directories-configuration-config-home
  37. home-xdg-base-directories-configuration-data-home
  38. home-xdg-base-directories-configuration-state-home
  39. home-xdg-base-directories-configuration-log-home
  40. home-xdg-base-directories-configuration-runtime-dir
  41. home-xdg-user-directories-service-type
  42. home-xdg-user-directories-configuration
  43. home-xdg-user-directories-configuration?
  44. home-xdg-user-directories-configuration-desktop
  45. home-xdg-user-directories-configuration-documents
  46. home-xdg-user-directories-configuration-download
  47. home-xdg-user-directories-configuration-music
  48. home-xdg-user-directories-configuration-pictures
  49. home-xdg-user-directories-configuration-publicshare
  50. home-xdg-user-directories-configuration-templates
  51. home-xdg-user-directories-configuration-videos
  52. xdg-desktop-action
  53. xdg-desktop-entry
  54. home-xdg-mime-applications-service-type
  55. home-xdg-mime-applications-configuration))
  56. ;;; Commentary:
  57. ;;
  58. ;; This module contains services related to XDG directories and
  59. ;; applications.
  60. ;;
  61. ;; - XDG base directories
  62. ;; - XDG user directories
  63. ;; - XDG MIME applications
  64. ;;
  65. ;;; Code:
  66. ;;;
  67. ;;; XDG base directories.
  68. ;;;
  69. (define (serialize-path field-name val) "")
  70. (define path? string?)
  71. (define-configuration home-xdg-base-directories-configuration
  72. (cache-home
  73. (path "$HOME/.cache")
  74. "Base directory for programs to store user-specific non-essential
  75. (cached) data. Files in this directory can be deleted anytime without
  76. loss of important data.")
  77. (config-home
  78. (path "$HOME/.config")
  79. "Base directory for programs to store configuration files.
  80. Some programs store here log or state files, but it's not desired,
  81. this directory should contain static configurations.")
  82. (data-home
  83. (path "$HOME/.local/share")
  84. "Base directory for programs to store architecture independent
  85. read-only shared data, analogus to @file{/usr/share}, but for user.")
  86. (runtime-dir
  87. (path "${XDG_RUNTIME_DIR:-/run/user/$UID}")
  88. "Base directory for programs to store user-specific runtime files,
  89. like sockets.")
  90. (log-home
  91. (path "$HOME/.local/var/log")
  92. "Base directory for programs to store log files, analogus to
  93. @file{/var/log}, but for user. It is not a part of XDG Base Directory
  94. Specification, but helps to make implementation of home services more
  95. consistent.")
  96. (state-home
  97. (path "$HOME/.local/state")
  98. "Base directory for programs to store state data that should persist
  99. between (application) restarts, such as logs, but are not important or
  100. portable enough to the user to warrant storing them in
  101. @env{XDG_DATA_HOME}."))
  102. (define (home-xdg-base-directories-environment-variables-service config)
  103. (map
  104. (lambda (field)
  105. (cons (format
  106. #f "XDG_~a"
  107. (object->snake-case-string (configuration-field-name field) 'upper))
  108. ((configuration-field-getter field) config)))
  109. home-xdg-base-directories-configuration-fields))
  110. (define (ensure-xdg-base-dirs-on-activation config)
  111. (with-imported-modules '((guix build utils))
  112. #~(begin
  113. (use-modules (guix build utils))
  114. (map (lambda (xdg-base-dir-variable)
  115. (mkdir-p
  116. (getenv
  117. xdg-base-dir-variable)))
  118. '#$(filter-map
  119. (lambda (field)
  120. (let ((variable
  121. (string-append
  122. "XDG_"
  123. (object->snake-case-string
  124. (configuration-field-name field) 'upper))))
  125. ;; XDG_RUNTIME_DIR shouldn't be created during activation
  126. ;; and will be provided by elogind or other service.
  127. (and (not (string=? "XDG_RUNTIME_DIR" variable))
  128. variable)))
  129. home-xdg-base-directories-configuration-fields)))))
  130. (define (last-extension-or-cfg config extensions)
  131. "Picks configuration value from last provided extension. If there
  132. are no extensions use configuration instead."
  133. (or (and (not (null? extensions)) (last extensions)) config))
  134. (define home-xdg-base-directories-service-type
  135. (service-type (name 'home-xdg-base-directories)
  136. (extensions
  137. (list (service-extension
  138. home-environment-variables-service-type
  139. home-xdg-base-directories-environment-variables-service)
  140. (service-extension
  141. home-activation-service-type
  142. ensure-xdg-base-dirs-on-activation)))
  143. (default-value (home-xdg-base-directories-configuration))
  144. (compose identity)
  145. (extend last-extension-or-cfg)
  146. (description "Configure XDG base directories. This
  147. service introduces an additional @env{XDG_LOG_HOME} variable. It's not
  148. a part of XDG specification, at least yet, but are convenient to have,
  149. it improves the consistency between different home services. The
  150. services of this service-type is instantiated by default, to provide
  151. non-default value, extend the service-type (using @code{simple-service}
  152. for example).")))
  153. (define (generate-home-xdg-base-directories-documentation)
  154. (generate-documentation
  155. `((home-xdg-base-directories-configuration
  156. ,home-xdg-base-directories-configuration-fields))
  157. 'home-xdg-base-directories-configuration))
  158. ;;;
  159. ;;; XDG user directories.
  160. ;;;
  161. (define (serialize-string field-name val)
  162. ;; The path has to be quoted
  163. (format #f "XDG_~a_DIR=\"~a\"\n"
  164. (object->snake-case-string field-name 'upper) val))
  165. (define-configuration home-xdg-user-directories-configuration
  166. (desktop
  167. (string "$HOME/Desktop")
  168. "Default ``desktop'' directory, this is what you see on your
  169. desktop when using a desktop environment,
  170. e.g. GNOME (@pxref{XWindow,,,guix.info}).")
  171. (documents
  172. (string "$HOME/Documents")
  173. "Default directory to put documents like PDFs.")
  174. (download
  175. (string "$HOME/Downloads")
  176. "Default directory downloaded files, this is where your Web-broser
  177. will put downloaded files in.")
  178. (music
  179. (string "$HOME/Music")
  180. "Default directory for audio files.")
  181. (pictures
  182. (string "$HOME/Pictures")
  183. "Default directory for pictures and images.")
  184. (publicshare
  185. (string "$HOME/Public")
  186. "Default directory for shared files, which can be accessed by other
  187. users on local machine or via network.")
  188. (templates
  189. (string "$HOME/Templates")
  190. "Default directory for templates. They can be used by graphical
  191. file manager or other apps for creating new files with some
  192. pre-populated content.")
  193. (videos
  194. (string "$HOME/Videos")
  195. "Default directory for videos."))
  196. (define (home-xdg-user-directories-files-service config)
  197. `(("user-dirs.conf"
  198. ,(mixed-text-file
  199. "user-dirs.conf"
  200. "enabled=False\n"))
  201. ("user-dirs.dirs"
  202. ,(mixed-text-file
  203. "user-dirs.dirs"
  204. (serialize-configuration
  205. config
  206. home-xdg-user-directories-configuration-fields)))))
  207. (define (home-xdg-user-directories-activation-service config)
  208. (let ((dirs (map (lambda (field)
  209. ((configuration-field-getter field) config))
  210. home-xdg-user-directories-configuration-fields)))
  211. #~(let ((ensure-dir
  212. (lambda (path)
  213. ((@ (guix build utils) mkdir-p)
  214. ((@ (ice-9 string-fun) string-replace-substring)
  215. path "$HOME" (getenv "HOME"))))))
  216. (display "Creating XDG user directories...")
  217. (map ensure-dir '#$dirs)
  218. (display " done\n"))))
  219. (define home-xdg-user-directories-service-type
  220. (service-type (name 'home-xdg-user-directories)
  221. (extensions
  222. (list (service-extension
  223. home-xdg-configuration-files-service-type
  224. home-xdg-user-directories-files-service)
  225. (service-extension
  226. home-activation-service-type
  227. home-xdg-user-directories-activation-service)))
  228. (default-value (home-xdg-user-directories-configuration))
  229. (compose identity)
  230. (extend last-extension-or-cfg)
  231. (description "Configure XDG user directories. To
  232. disable a directory, point it to the $HOME.")))
  233. (define (generate-home-xdg-user-directories-documentation)
  234. (generate-documentation
  235. `((home-xdg-user-directories-configuration
  236. ,home-xdg-user-directories-configuration-fields))
  237. 'home-xdg-user-directories-configuration))
  238. ;;;
  239. ;;; XDG MIME applications.
  240. ;;;
  241. ;; Example config
  242. ;;
  243. ;; (home-xdg-mime-applications-configuration
  244. ;; (added '((x-scheme-handler/magnet . torrent.desktop)))
  245. ;; (default '((inode/directory . file.desktop)))
  246. ;; (removed '((inode/directory . thunar.desktop)))
  247. ;; (desktop-entries
  248. ;; (list (xdg-desktop-entry
  249. ;; (file "file")
  250. ;; (name "File manager")
  251. ;; (type 'application)
  252. ;; (config
  253. ;; '((exec . "emacsclient -c -a emacs %u"))))
  254. ;; (xdg-desktop-entry
  255. ;; (file "text")
  256. ;; (name "Text editor")
  257. ;; (type 'application)
  258. ;; (config
  259. ;; '((exec . "emacsclient -c -a emacs %u")))
  260. ;; (actions
  261. ;; (list (xdg-desktop-action
  262. ;; (action 'create)
  263. ;; (name "Create an action")
  264. ;; (config
  265. ;; '((exec . "echo hi"))))))))))
  266. ;; See
  267. ;; <https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html>
  268. ;; <https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html>
  269. (define (serialize-alist field-name val)
  270. (define (serialize-mimelist-entry key val)
  271. (let ((val (cond
  272. ((list? val)
  273. (string-join (map maybe-object->string val) ";"))
  274. ((or (string? val) (symbol? val))
  275. val)
  276. (else (raise (formatted-message
  277. (G_ "\
  278. The value of an XDG MIME entry must be a list, string or symbol, was given ~a")
  279. val))))))
  280. (format #f "~a=~a\n" key val)))
  281. (define (merge-duplicates alist acc)
  282. "Merge values that have the same key.
  283. @example
  284. (merge-duplicates '((key1 . value1)
  285. (key2 . value2)
  286. (key1 . value3)
  287. (key1 . value4)) '())
  288. @result{} ((key1 . (value4 value3 value1)) (key2 . value2))
  289. @end example"
  290. (cond
  291. ((null? alist) acc)
  292. (else (let* ((head (first alist))
  293. (tail (cdr alist))
  294. (key (first head))
  295. (value (cdr head))
  296. (duplicate? (assoc key acc))
  297. (ensure-list (lambda (x)
  298. (if (list? x) x (list x)))))
  299. (if duplicate?
  300. ;; XXX: This will change the order of things,
  301. ;; though, it shouldn't be a problem for XDG MIME.
  302. (merge-duplicates
  303. tail
  304. (alist-cons key
  305. (cons value (ensure-list (cdr duplicate?)))
  306. (alist-delete key acc)))
  307. (merge-duplicates tail (cons head acc)))))))
  308. (string-append (if (equal? field-name 'default)
  309. "\n[Default Applications]\n"
  310. (format #f "\n[~a Associations]\n"
  311. (string-capitalize (symbol->string field-name))))
  312. (generic-serialize-alist string-append
  313. serialize-mimelist-entry
  314. (merge-duplicates val '()))))
  315. (define xdg-desktop-types (make-enumeration
  316. '(application
  317. link
  318. directory)))
  319. (define (xdg-desktop-type? type)
  320. (unless (enum-set-member? type xdg-desktop-types)
  321. (raise (formatted-message
  322. (G_ "XDG desktop type must be of of ~a, was given: ~a")
  323. (list->human-readable-list (enum-set->list xdg-desktop-types))
  324. type))))
  325. ;; TODO: Add proper docs for this
  326. ;; XXX: 'define-configuration' require that fields have a default
  327. ;; value.
  328. (define-record-type* <xdg-desktop-action>
  329. xdg-desktop-action make-xdg-desktop-action
  330. xdg-desktop-action?
  331. (action xdg-desktop-action-action) ; symbol
  332. (name xdg-desktop-action-name) ; string
  333. (config xdg-desktop-action-config ; alist
  334. (default '())))
  335. (define-record-type* <xdg-desktop-entry>
  336. xdg-desktop-entry make-xdg-desktop-entry
  337. xdg-desktop-entry?
  338. ;; ".desktop" will automatically be added
  339. (file xdg-desktop-entry-file) ; string
  340. (name xdg-desktop-entry-name) ; string
  341. (type xdg-desktop-entry-type) ; xdg-desktop-type
  342. (config xdg-desktop-entry-config ; alist
  343. (default '()))
  344. (actions xdg-desktop-entry-actions ; list of <xdg-desktop-action>
  345. (default '())))
  346. (define desktop-entries? (list-of xdg-desktop-entry?))
  347. (define (serialize-desktop-entries field-name val) "")
  348. (define (serialize-xdg-desktop-entry entry)
  349. "Return a tuple of the file name for ENTRY and the serialized
  350. configuration."
  351. (define (format-config key val)
  352. (let ((val (cond
  353. ((list? val)
  354. (string-join (map maybe-object->string val) ";"))
  355. ((boolean? val)
  356. (if val "true" "false"))
  357. (else val)))
  358. (key (string-capitalize (maybe-object->string key))))
  359. (list (if (string-suffix? key "?")
  360. (string-drop-right key (- (string-length key) 1))
  361. key)
  362. "=" val "\n")))
  363. (define (serialize-alist config)
  364. (generic-serialize-alist append format-config config))
  365. (define (serialize-xdg-desktop-action desktop-action)
  366. (match-record desktop-action <xdg-desktop-action>
  367. (action name config)
  368. `(,(format #f "[Desktop Action ~a]\n"
  369. (string-capitalize (maybe-object->string action)))
  370. ,(format #f "Name=~a\n" name)
  371. ,@(serialize-alist config))))
  372. (match-record entry <xdg-desktop-entry>
  373. (file name type config actions)
  374. (list (if (string-suffix? file ".desktop")
  375. file
  376. (string-append file ".desktop"))
  377. `("[Desktop Entry]\n"
  378. ,(format #f "Name=~a\n" name)
  379. ,(format #f "Type=~a\n"
  380. (string-capitalize (symbol->string type)))
  381. ,@(serialize-alist config)
  382. ,@(append-map serialize-xdg-desktop-action actions)))))
  383. (define-configuration home-xdg-mime-applications-configuration
  384. (added
  385. (alist '())
  386. "An association list of MIME types and desktop entries which indicate
  387. that the application should used to open the specified MIME type. The
  388. value has to be string, symbol, or list of strings or symbols, this
  389. applies to the `@code{default}', and `@code{removed}' fields as well.")
  390. (default
  391. (alist '())
  392. "An association list of MIME types and desktop entries which indicate
  393. that the application should be the default for opening the specified
  394. MIME type.")
  395. (removed
  396. (alist '())
  397. "An association list of MIME types and desktop entries which indicate
  398. that the application cannot open the specified MIME type.")
  399. (desktop-entries
  400. (desktop-entries '())
  401. "A list of XDG desktop entries to create. See
  402. @code{xdg-desktop-entry}."))
  403. (define (home-xdg-mime-applications-files config)
  404. (define (add-xdg-desktop-entry-file entry)
  405. (let ((file (first entry))
  406. (config (second entry)))
  407. ;; TODO: Use xdg-data-files instead of home-files here
  408. (list (format #f "applications/~a" file)
  409. (apply mixed-text-file
  410. (format #f "xdg-desktop-~a-entry" file)
  411. config))))
  412. (map (compose add-xdg-desktop-entry-file serialize-xdg-desktop-entry)
  413. (home-xdg-mime-applications-configuration-desktop-entries config)))
  414. (define (home-xdg-mime-applications-xdg-files config)
  415. `(("mimeapps.list"
  416. ,(mixed-text-file
  417. "xdg-mime-appplications"
  418. (serialize-configuration
  419. config
  420. home-xdg-mime-applications-configuration-fields)))))
  421. (define (home-xdg-mime-applications-extension old-config extension-configs)
  422. (define (extract-fields config)
  423. ;; return '(added default removed desktop-entries)
  424. (list (home-xdg-mime-applications-configuration-added config)
  425. (home-xdg-mime-applications-configuration-default config)
  426. (home-xdg-mime-applications-configuration-removed config)
  427. (home-xdg-mime-applications-configuration-desktop-entries config)))
  428. (define (append-configs elem acc)
  429. (list (append (first elem) (first acc))
  430. (append (second elem) (second acc))
  431. (append (third elem) (third acc))
  432. (append (fourth elem) (fourth acc))))
  433. ;; TODO: Implement procedure to check for duplicates without
  434. ;; sacrificing performance.
  435. ;;
  436. ;; Combine all the alists from 'added', 'default' and 'removed'
  437. ;; into one big alist.
  438. (let ((folded-configs (fold append-configs
  439. (extract-fields old-config)
  440. (map extract-fields extension-configs))))
  441. (home-xdg-mime-applications-configuration
  442. (added (first folded-configs))
  443. (default (second folded-configs))
  444. (removed (third folded-configs))
  445. (desktop-entries (fourth folded-configs)))))
  446. (define home-xdg-mime-applications-service-type
  447. (service-type (name 'home-xdg-mime-applications)
  448. (extensions
  449. (list (service-extension
  450. home-xdg-data-files-service-type
  451. home-xdg-mime-applications-files)
  452. (service-extension
  453. home-xdg-configuration-files-service-type
  454. home-xdg-mime-applications-xdg-files)))
  455. (compose identity)
  456. (extend home-xdg-mime-applications-extension)
  457. (default-value (home-xdg-mime-applications-configuration))
  458. (description
  459. "Configure XDG MIME applications, and XDG desktop entries.")))