1{ config, lib, pkgs, utils, ... }:
2
3let
4 xcfg = config.services.xserver;
5 cfg = xcfg.desktopManager.plasma5;
6
7 # Use only for **internal** options.
8 # This is not exactly user-friendly.
9 kdeConfigurationType = with types;
10 let
11 valueTypes = (oneOf [
12 bool
13 float
14 int
15 str
16 ]) // {
17 description = "KDE Configuration value";
18 emptyValue.value = "";
19 };
20 set = (nullOr (lazyAttrsOf valueTypes)) // {
21 description = "KDE Configuration set";
22 emptyValue.value = {};
23 };
24 in (lazyAttrsOf set) // {
25 description = "KDE Configuration file";
26 emptyValue.value = {};
27 };
28
29 libsForQt5 = pkgs.plasma5Packages;
30 inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
31 inherit (lib)
32 getBin optionalString literalExpression
33 mkRemovedOptionModule mkRenamedOptionModule
34 mkDefault mkIf mkMerge mkOption mkPackageOptionMD types;
35
36 activationScript = ''
37 ${set_XDG_CONFIG_HOME}
38
39 # The KDE icon cache is supposed to update itself automatically, but it uses
40 # the timestamp on the icon theme directory as a trigger. This doesn't work
41 # on NixOS because the timestamp never changes. As a workaround, delete the
42 # icon cache at login and session activation.
43 # See also: http://lists-archives.org/kde-devel/26175-what-when-will-icon-cache-refresh.html
44 rm -fv $HOME/.cache/icon-cache.kcache
45
46 # xdg-desktop-settings generates this empty file but
47 # it makes kbuildsyscoca5 fail silently. To fix this
48 # remove that menu if it exists.
49 rm -fv ''${XDG_CONFIG_HOME}/menus/applications-merged/xdg-desktop-menu-dummy.menu
50
51 # Qt writes a weird ‘libraryPath’ line to
52 # ~/.config/Trolltech.conf that causes the KDE plugin
53 # paths of previous KDE invocations to be searched.
54 # Obviously using mismatching KDE libraries is potentially
55 # disastrous, so here we nuke references to the Nix store
56 # in Trolltech.conf. A better solution would be to stop
57 # Qt from doing this wackiness in the first place.
58 trolltech_conf="''${XDG_CONFIG_HOME}/Trolltech.conf"
59 if [ -e "$trolltech_conf" ]; then
60 ${getBin pkgs.gnused}/bin/sed -i "$trolltech_conf" -e '/nix\\store\|nix\/store/ d'
61 fi
62
63 # Remove the kbuildsyscoca5 cache. It will be regenerated
64 # immediately after. This is necessary for kbuildsyscoca5 to
65 # recognize that software that has been removed.
66 rm -fv $HOME/.cache/ksycoca*
67
68 ${libsForQt5.kservice}/bin/kbuildsycoca5
69 '';
70
71 set_XDG_CONFIG_HOME = ''
72 # Set the default XDG_CONFIG_HOME if it is unset.
73 # Per the XDG Base Directory Specification:
74 # https://specifications.freedesktop.org/basedir-spec/latest
75 # 1. Never export this variable! If it is unset, then child processes are
76 # expected to set the default themselves.
77 # 2. Contaminate / if $HOME is unset; do not check if $HOME is set.
78 XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
79 '';
80
81in
82
83{
84 options = {
85 services.xserver.desktopManager.plasma5 = {
86 enable = mkOption {
87 type = types.bool;
88 default = false;
89 description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
90 };
91
92 phononBackend = mkOption {
93 type = types.enum [ "gstreamer" "vlc" ];
94 default = "vlc";
95 example = "gstreamer";
96 description = lib.mdDoc "Phonon audio backend to install.";
97 };
98
99 useQtScaling = mkOption {
100 type = types.bool;
101 default = false;
102 description = lib.mdDoc "Enable HiDPI scaling in Qt.";
103 };
104
105 runUsingSystemd = mkOption {
106 description = lib.mdDoc "Use systemd to manage the Plasma session";
107 type = types.bool;
108 default = true;
109 };
110
111 notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
112 default = [ "noto-fonts" ];
113 example = "noto-fonts-lgc-plus";
114 };
115
116 # Internally allows configuring kdeglobals globally
117 kdeglobals = mkOption {
118 internal = true;
119 default = {};
120 type = kdeConfigurationType;
121 };
122
123 # Internally allows configuring kwin globally
124 kwinrc = mkOption {
125 internal = true;
126 default = {};
127 type = kdeConfigurationType;
128 };
129
130 mobile.enable = mkOption {
131 type = types.bool;
132 default = false;
133 description = lib.mdDoc ''
134 Enable support for running the Plasma Mobile shell.
135 '';
136 };
137
138 mobile.installRecommendedSoftware = mkOption {
139 type = types.bool;
140 default = true;
141 description = lib.mdDoc ''
142 Installs software recommended for use with Plasma Mobile, but which
143 is not strictly required for Plasma Mobile to run.
144 '';
145 };
146
147 bigscreen.enable = mkOption {
148 type = types.bool;
149 default = false;
150 description = lib.mdDoc ''
151 Enable support for running the Plasma Bigscreen session.
152 '';
153 };
154 };
155 environment.plasma5.excludePackages = mkOption {
156 description = lib.mdDoc "List of default packages to exclude from the configuration";
157 type = types.listOf types.package;
158 default = [];
159 example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
160 };
161 };
162
163 imports = [
164 (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.")
165 (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "supportDDC" ] "DDC/CI is no longer supported upstream.")
166 (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
167 (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "excludePackages" ] [ "environment" "plasma5" "excludePackages" ])
168 ];
169
170 config = mkMerge [
171 # Common Plasma dependencies
172 (mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) {
173
174 security.wrappers = {
175 kscreenlocker_greet = {
176 setuid = true;
177 owner = "root";
178 group = "root";
179 source = "${getBin libsForQt5.kscreenlocker}/libexec/kscreenlocker_greet";
180 };
181 start_kdeinit = {
182 setuid = true;
183 owner = "root";
184 group = "root";
185 source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
186 };
187 kwin_wayland = {
188 owner = "root";
189 group = "root";
190 capabilities = "cap_sys_nice+ep";
191 source = "${getBin plasma5.kwin}/bin/kwin_wayland";
192 };
193 };
194
195 environment.systemPackages =
196 with libsForQt5;
197 with plasma5; with kdeGear; with kdeFrameworks;
198 let
199 requiredPackages = [
200 frameworkintegration
201 kactivities
202 kauth
203 kcmutils
204 kconfig
205 kconfigwidgets
206 kcoreaddons
207 kdoctools
208 kdbusaddons
209 kdeclarative
210 kded
211 kdesu
212 kdnssd
213 kemoticons
214 kfilemetadata
215 kglobalaccel
216 kguiaddons
217 kiconthemes
218 kidletime
219 kimageformats
220 kinit
221 kirigami2 # In system profile for SDDM theme. TODO: wrapper.
222 kio
223 kjobwidgets
224 knewstuff
225 knotifications
226 knotifyconfig
227 kpackage
228 kparts
229 kpeople
230 krunner
231 kservice
232 ktextwidgets
233 kwallet
234 kwallet-pam
235 kwalletmanager
236 kwayland
237 kwayland-integration
238 kwidgetsaddons
239 kxmlgui
240 kxmlrpcclient
241 plasma-framework
242 solid
243 sonnet
244 threadweaver
245
246 breeze-qt5
247 kactivitymanagerd
248 kde-cli-tools
249 kdecoration
250 kdeplasma-addons
251 kgamma5
252 khotkeys
253 kscreen
254 kscreenlocker
255 kwayland
256 kwin
257 kwrited
258 libkscreen
259 libksysguard
260 milou
261 plasma-integration
262 polkit-kde-agent
263
264 plasma-desktop
265 plasma-workspace
266 plasma-workspace-wallpapers
267
268 oxygen-sounds
269
270 breeze-icons
271 pkgs.hicolor-icon-theme
272
273 kde-gtk-config
274 breeze-gtk
275
276 qtvirtualkeyboard
277
278 pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
279 ];
280 optionalPackages = [
281 pkgs.aha # needed by kinfocenter for fwupd support
282 plasma-browser-integration
283 konsole
284 oxygen
285 (lib.getBin qttools) # Expose qdbus in PATH
286 ];
287 in
288 requiredPackages
289 ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages
290
291 # Phonon audio backend
292 ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
293 ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
294
295 # Optional hardware support features
296 ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt pkgs.openobex pkgs.obexftp ]
297 ++ lib.optional config.networking.networkmanager.enable plasma-nm
298 ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
299 ++ lib.optional config.services.pipewire.pulse.enable plasma-pa
300 ++ lib.optional config.powerManagement.enable powerdevil
301 ++ lib.optional config.services.colord.enable pkgs.colord-kde
302 ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt
303 ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
304 ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet
305 ++ lib.optional config.services.flatpak.enable flatpak-kcm;
306
307 # Extra services for D-Bus activation
308 services.dbus.packages = [
309 plasma5.kactivitymanagerd
310 ];
311
312 environment.pathsToLink = [
313 # FIXME: modules should link subdirs of `/share` rather than relying on this
314 "/share"
315 ];
316
317 environment.etc."X11/xkb".source = xcfg.xkbDir;
318
319 environment.sessionVariables = {
320 PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
321
322 # Needed for things that depend on other store.kde.org packages to install correctly,
323 # notably Plasma look-and-feel packages (a.k.a. Global Themes)
324 #
325 # FIXME: this is annoyingly impure and should really be fixed at source level somehow,
326 # but kpackage is a library so we can't just wrap the one thing invoking it and be done.
327 # This also means things won't work for people not on Plasma, but at least this way it
328 # works for SOME people.
329 KPACKAGE_DEP_RESOLVERS_PATH = "${pkgs.plasma5Packages.frameworkintegration.out}/libexec/kf5/kpackagehandlers";
330 };
331
332 # Enable GTK applications to load SVG icons
333 services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
334
335 fonts.fonts = with pkgs; [ cfg.notoPackage hack-font ];
336 fonts.fontconfig.defaultFonts = {
337 monospace = [ "Hack" "Noto Sans Mono" ];
338 sansSerif = [ "Noto Sans" ];
339 serif = [ "Noto Serif" ];
340 };
341
342 programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
343
344 # Enable helpful DBus services.
345 services.accounts-daemon.enable = true;
346 # when changing an account picture the accounts-daemon reads a temporary file containing the image which systemsettings5 may place under /tmp
347 systemd.services.accounts-daemon.serviceConfig.PrivateTmp = false;
348 services.power-profiles-daemon.enable = mkDefault true;
349 services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true);
350 services.udisks2.enable = true;
351 services.upower.enable = config.powerManagement.enable;
352 services.xserver.libinput.enable = mkDefault true;
353
354 # Extra UDEV rules used by Solid
355 services.udev.packages = [
356 # libmtp has "bin", "dev", "out" outputs. UDEV rules file is in "out".
357 pkgs.libmtp.out
358 pkgs.media-player-info
359 ];
360
361 services.xserver.displayManager.sddm = {
362 theme = mkDefault "breeze";
363 };
364
365 security.pam.services.kde = { allowNullPassword = true; };
366
367 security.pam.services.login.enableKwallet = true;
368
369 systemd.user.services = {
370 plasma-early-setup = mkIf cfg.runUsingSystemd {
371 description = "Early Plasma setup";
372 wantedBy = [ "graphical-session-pre.target" ];
373 serviceConfig.Type = "oneshot";
374 script = activationScript;
375 };
376 };
377
378 xdg.portal.enable = true;
379 xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
380 # xdg-desktop-portal-kde expects PipeWire to be running.
381 # This does not, by default, replace PulseAudio.
382 services.pipewire.enable = mkDefault true;
383
384 # Update the start menu for each user that is currently logged in
385 system.userActivationScripts.plasmaSetup = activationScript;
386
387 nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
388 })
389
390 (mkIf (cfg.kwinrc != {}) {
391 environment.etc."xdg/kwinrc".text = lib.generators.toINI {} cfg.kwinrc;
392 })
393
394 (mkIf (cfg.kdeglobals != {}) {
395 environment.etc."xdg/kdeglobals".text = lib.generators.toINI {} cfg.kdeglobals;
396 })
397
398 # Plasma Desktop
399 (mkIf cfg.enable {
400
401 # Seed our configuration into nixos-generate-config
402 system.nixos-generate-config.desktopConfiguration = [
403 ''
404 # Enable the Plasma 5 Desktop Environment.
405 services.xserver.displayManager.sddm.enable = true;
406 services.xserver.desktopManager.plasma5.enable = true;
407 ''
408 ];
409
410 services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-workspace ];
411 # Default to be `plasma` (X11) instead of `plasmawayland`, since plasma wayland currently has
412 # many tiny bugs.
413 # See: https://github.com/NixOS/nixpkgs/issues/143272
414 services.xserver.displayManager.defaultSession = mkDefault "plasma";
415
416 environment.systemPackages =
417 with libsForQt5;
418 with plasma5; with kdeGear; with kdeFrameworks;
419 let
420 requiredPackages = [
421 ksystemstats
422 kinfocenter
423 kmenuedit
424 plasma-systemmonitor
425 spectacle
426 systemsettings
427
428 dolphin
429 dolphin-plugins
430 ffmpegthumbs
431 kdegraphics-thumbnailers
432 kde-inotify-survey
433 kio-admin
434 kio-extras
435 ];
436 optionalPackages = [
437 ark
438 elisa
439 gwenview
440 okular
441 khelpcenter
442 print-manager
443 ];
444 in requiredPackages ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages;
445
446 systemd.user.services = {
447 plasma-run-with-systemd = {
448 description = "Run KDE Plasma via systemd";
449 wantedBy = [ "basic.target" ];
450 serviceConfig.Type = "oneshot";
451 script = ''
452 ${set_XDG_CONFIG_HOME}
453
454 ${kdeFrameworks.kconfig}/bin/kwriteconfig5 \
455 --file startkderc --group General --key systemdBoot ${lib.boolToString cfg.runUsingSystemd}
456 '';
457 };
458 };
459 })
460
461 # Plasma Mobile
462 (mkIf cfg.mobile.enable {
463 assertions = [
464 {
465 # The user interface breaks without NetworkManager
466 assertion = config.networking.networkmanager.enable;
467 message = "Plasma Mobile requires NetworkManager.";
468 }
469 {
470 # The user interface breaks without bluetooth
471 assertion = config.hardware.bluetooth.enable;
472 message = "Plasma Mobile requires Bluetooth.";
473 }
474 {
475 # The user interface breaks without pulse
476 assertion = config.hardware.pulseaudio.enable || (config.services.pipewire.enable && config.services.pipewire.pulse.enable);
477 message = "Plasma Mobile requires pulseaudio.";
478 }
479 ];
480
481 environment.systemPackages =
482 with libsForQt5;
483 with plasma5; with kdeApplications; with kdeFrameworks;
484 [
485 # Basic packages without which Plasma Mobile fails to work properly.
486 plasma-mobile
487 plasma-nano
488 pkgs.maliit-framework
489 pkgs.maliit-keyboard
490 ]
491 ++ lib.optionals (cfg.mobile.installRecommendedSoftware) (with libsForQt5.plasmaMobileGear;[
492 # Additional software made for Plasma Mobile.
493 alligator
494 angelfish
495 audiotube
496 calindori
497 kalk
498 kasts
499 kclock
500 keysmith
501 koko
502 krecorder
503 ktrip
504 kweather
505 plasma-dialer
506 plasma-phonebook
507 plasma-settings
508 spacebar
509 ])
510 ;
511
512 # The following services are needed or the UI is broken.
513 hardware.bluetooth.enable = true;
514 hardware.pulseaudio.enable = true;
515 networking.networkmanager.enable = true;
516 # Required for autorotate
517 hardware.sensor.iio.enable = lib.mkDefault true;
518
519 # Recommendations can be found here:
520 # - https://invent.kde.org/plasma-mobile/plasma-phone-settings/-/tree/master/etc/xdg
521 # This configuration is the minimum required for Plasma Mobile to *work*.
522 services.xserver.desktopManager.plasma5 = {
523 kdeglobals = {
524 KDE = {
525 # This forces a numeric PIN for the lockscreen, which is the
526 # recommendation from upstream.
527 LookAndFeelPackage = lib.mkDefault "org.kde.plasma.phone";
528 };
529 };
530 kwinrc = {
531 "Wayland" = {
532 "InputMethod[$e]" = "/run/current-system/sw/share/applications/com.github.maliit.keyboard.desktop";
533 "VirtualKeyboardEnabled" = "true";
534 };
535 "org.kde.kdecoration2" = {
536 # No decorations (title bar)
537 NoPlugin = lib.mkDefault "true";
538 };
539 };
540 };
541
542 services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-mobile ];
543 })
544
545 # Plasma Bigscreen
546 (mkIf cfg.bigscreen.enable {
547 environment.systemPackages =
548 with pkgs.plasma5Packages;
549 [
550 plasma-nano
551 plasma-settings
552 plasma-bigscreen
553 plasma-remotecontrollers
554
555 aura-browser
556 plank-player
557
558 plasma-pa
559 plasma-nm
560 kdeconnect-kde
561 ];
562
563 services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-bigscreen ];
564
565 # required for plasma-remotecontrollers to work correctly
566 hardware.uinput.enable = true;
567 })
568 ];
569}