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