diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix index 560d9a6c4..b8ca9f02d 100644 --- a/modules/services/emacs.nix +++ b/modules/services/emacs.nix @@ -9,6 +9,9 @@ let emacsBinPath = "${cfg.package}/bin"; emacsVersion = getVersion cfg.package; + clientWMClass = + if versionAtLeast emacsVersion "28" then "Emacsd" else "Emacs"; + # Adapted from upstream emacs.desktop clientDesktopItem = pkgs.writeTextDir "share/applications/emacsclient.desktop" (generators.toINI { } { @@ -24,26 +27,15 @@ let GenericName = "Text Editor"; MimeType = "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;"; - Categories = "Utility;TextEditor;"; - StartupWMClass = "Emacs"; + Categories = "Development;TextEditor;"; + Keywords = "Text;Editor;"; + StartupWMClass = clientWMClass; }; }); # Match the default socket path for the Emacs version so emacsclient continues - # to work without wrapping it. It might be worthwhile to allow customizing the - # socket path, but we would want to wrap emacsclient in the user profile to - # connect to the alternative socket by default for Emacs 26, and set - # EMACS_SOCKET_NAME for Emacs 27. - # - # As systemd doesn't perform variable expansion for the ListenStream param, we - # would also have to solve the problem of matching the shell path to the path - # used in the socket unit, which would likely involve templating. It seems of - # little value for the most common use case of one Emacs daemon per user - # session. - socketPath = if versionAtLeast emacsVersion "27" then - "%t/emacs/server" - else - "%T/emacs%U/server"; + # to work without wrapping it. + socketPath = "%t/emacs/server"; in { meta.maintainers = [ maintainers.tadfisher ]; @@ -82,24 +74,25 @@ in { config = mkIf cfg.enable (mkMerge [ { - assertions = [{ - assertion = cfg.socketActivation.enable - -> versionAtLeast emacsVersion "26"; - message = "Socket activation requires Emacs 26 or newer."; - }]; - systemd.user.services.emacs = { Unit = { - Description = "Emacs: the extensible, self-documenting text editor"; + Description = "Emacs text editor"; Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/"; # Avoid killing the Emacs session, which may be full of # unsaved buffers. X-RestartIfChanged = false; + } // optionalAttrs (cfg.socketActivation.enable) { + # Emacs deletes its socket when shutting down, which systemd doesn't + # handle, resulting in a server without a socket. + # See https://github.com/nix-community/home-manager/issues/2018 + RefuseManualStart = true; }; Service = { + Type = "notify"; + # We wrap ExecStart in a login shell so Emacs starts with the user's # environment, most importantly $PATH and $NIX_PROFILES. It may be # worth investigating a more targeted approach for user services to @@ -113,9 +106,11 @@ in { optionalString cfg.socketActivation.enable "=${escapeShellArg socketPath}" }"''; - # We use '(kill-emacs 0)' to avoid exiting with a failure code, which - # would restart the service immediately. - ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'"; + + # Emacs will exit with status 15 after having received SIGTERM, which + # is the default "KillSignal" value systemd uses to stop services. + SuccessExitStatus = 15; + Restart = "on-failure"; }; } // optionalAttrs (!cfg.socketActivation.enable) { @@ -128,7 +123,7 @@ in { (mkIf cfg.socketActivation.enable { systemd.user.sockets.emacs = { Unit = { - Description = "Emacs: the extensible, self-documenting text editor"; + Description = "Emacs text editor"; Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/"; }; diff --git a/tests/modules/services/emacs/default.nix b/tests/modules/services/emacs/default.nix index af27538d9..86f68c4c8 100644 --- a/tests/modules/services/emacs/default.nix +++ b/tests/modules/services/emacs/default.nix @@ -1,5 +1,6 @@ { - emacs-service = ./emacs-service.nix; - emacs-socket-26 = ./emacs-socket-26.nix; + emacs-service-27 = ./emacs-service-27.nix; + emacs-service-28 = ./emacs-service-28.nix; emacs-socket-27 = ./emacs-socket-27.nix; + emacs-socket-28 = ./emacs-socket-28.nix; } diff --git a/tests/modules/services/emacs/emacs-emacsclient.desktop b/tests/modules/services/emacs/emacs-27-emacsclient.desktop similarity index 87% rename from tests/modules/services/emacs/emacs-emacsclient.desktop rename to tests/modules/services/emacs/emacs-27-emacsclient.desktop index 1dafb4e0f..89503c790 100644 --- a/tests/modules/services/emacs/emacs-emacsclient.desktop +++ b/tests/modules/services/emacs/emacs-27-emacsclient.desktop @@ -1,9 +1,10 @@ [Desktop Entry] -Categories=Utility;TextEditor; +Categories=Development;TextEditor; Comment=Edit text Exec=@emacs@/bin/emacsclient -c %F GenericName=Text Editor Icon=emacs +Keywords=Text;Editor; MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; Name=Emacs Client StartupWMClass=Emacs diff --git a/tests/modules/services/emacs/emacs-28-emacsclient.desktop b/tests/modules/services/emacs/emacs-28-emacsclient.desktop new file mode 100644 index 000000000..96b727525 --- /dev/null +++ b/tests/modules/services/emacs/emacs-28-emacsclient.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Development;TextEditor; +Comment=Edit text +Exec=@emacs@/bin/emacsclient -c %F +GenericName=Text Editor +Icon=emacs +Keywords=Text;Editor; +MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; +Name=Emacs Client +StartupWMClass=Emacsd +Terminal=false +Type=Application diff --git a/tests/modules/services/emacs/emacs-service.nix b/tests/modules/services/emacs/emacs-service-27.nix similarity index 89% rename from tests/modules/services/emacs/emacs-service.nix rename to tests/modules/services/emacs/emacs-service-27.nix index be27e9ab3..fa5f9f62b 100644 --- a/tests/modules/services/emacs/emacs-service.nix +++ b/tests/modules/services/emacs/emacs-service-27.nix @@ -6,7 +6,7 @@ with lib; config = { nixpkgs.overlays = [ (self: super: rec { - emacs = pkgs.writeShellScriptBin "dummy-emacs" "" // { + emacs = pkgs.writeShellScriptBin "dummy-emacs-27.2" "" // { outPath = "@emacs@"; }; emacsPackagesFor = _: @@ -31,7 +31,7 @@ with lib; } } assertFileContent home-path/share/applications/emacsclient.desktop \ - ${./emacs-emacsclient.desktop} + ${./emacs-27-emacsclient.desktop} ''; }; } diff --git a/tests/modules/services/emacs/emacs-service-28.nix b/tests/modules/services/emacs/emacs-service-28.nix new file mode 100644 index 000000000..092cd1453 --- /dev/null +++ b/tests/modules/services/emacs/emacs-service-28.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + nixpkgs.overlays = [ + (self: super: rec { + emacs = pkgs.writeShellScriptBin "dummy-emacs-28.0.5" "" // { + outPath = "@emacs@"; + }; + emacsPackagesFor = _: + makeScope super.newScope (_: { emacsWithPackages = _: emacs; }); + }) + ]; + + programs.emacs.enable = true; + services.emacs.enable = true; + services.emacs.client.enable = true; + + nmt.script = '' + assertPathNotExists home-files/.config/systemd/user/emacs.socket + assertFileExists home-files/.config/systemd/user/emacs.service + assertFileExists home-path/share/applications/emacsclient.desktop + + assertFileContent home-files/.config/systemd/user/emacs.service \ + ${ + pkgs.substituteAll { + inherit (pkgs) runtimeShell; + src = ./emacs-service-emacs.service; + } + } + assertFileContent home-path/share/applications/emacsclient.desktop \ + ${./emacs-28-emacsclient.desktop} + ''; + }; +} diff --git a/tests/modules/services/emacs/emacs-service-emacs.service b/tests/modules/services/emacs/emacs-service-emacs.service index d8a618a26..00b0c8eb2 100644 --- a/tests/modules/services/emacs/emacs-service-emacs.service +++ b/tests/modules/services/emacs/emacs-service-emacs.service @@ -3,10 +3,11 @@ WantedBy=default.target [Service] ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon" -ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)' Restart=on-failure +SuccessExitStatus=15 +Type=notify [Unit] -Description=Emacs: the extensible, self-documenting text editor +Description=Emacs text editor Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ X-RestartIfChanged=false diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.service b/tests/modules/services/emacs/emacs-socket-26-emacs.service deleted file mode 100644 index 2d731c7ee..000000000 --- a/tests/modules/services/emacs/emacs-socket-26-emacs.service +++ /dev/null @@ -1,9 +0,0 @@ -[Service] -ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%T/emacs%U/server'" -ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)' -Restart=on-failure - -[Unit] -Description=Emacs: the extensible, self-documenting text editor -Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ -X-RestartIfChanged=false diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.socket b/tests/modules/services/emacs/emacs-socket-26-emacs.socket deleted file mode 100644 index d2fa78e22..000000000 --- a/tests/modules/services/emacs/emacs-socket-26-emacs.socket +++ /dev/null @@ -1,12 +0,0 @@ -[Install] -WantedBy=sockets.target - -[Socket] -DirectoryMode=0700 -FileDescriptorName=server -ListenStream=%T/emacs%U/server -SocketMode=0600 - -[Unit] -Description=Emacs: the extensible, self-documenting text editor -Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ diff --git a/tests/modules/services/emacs/emacs-socket-27.nix b/tests/modules/services/emacs/emacs-socket-27.nix index 213dedca5..22619937d 100644 --- a/tests/modules/services/emacs/emacs-socket-27.nix +++ b/tests/modules/services/emacs/emacs-socket-27.nix @@ -8,7 +8,7 @@ in { config = { nixpkgs.overlays = [ (self: super: rec { - emacs = pkgs.writeShellScriptBin "dummy-emacs-27.0.91" "" // { + emacs = pkgs.writeShellScriptBin "dummy-emacs-27.2" "" // { outPath = "@emacs@"; }; emacsPackagesFor = _: @@ -27,16 +27,16 @@ in { assertFileExists home-path/share/applications/emacsclient.desktop assertFileContent home-files/.config/systemd/user/emacs.socket \ - ${./emacs-socket-27-emacs.socket} + ${./emacs-socket-emacs.socket} assertFileContent home-files/.config/systemd/user/emacs.service \ ${ pkgs.substituteAll { inherit (pkgs) runtimeShell; - src = ./emacs-socket-27-emacs.service; + src = ./emacs-socket-emacs.service; } } assertFileContent home-path/share/applications/emacsclient.desktop \ - ${./emacs-emacsclient.desktop} + ${./emacs-27-emacsclient.desktop} ''; }; } diff --git a/tests/modules/services/emacs/emacs-socket-26.nix b/tests/modules/services/emacs/emacs-socket-28.nix similarity index 80% rename from tests/modules/services/emacs/emacs-socket-26.nix rename to tests/modules/services/emacs/emacs-socket-28.nix index 65f06159e..f04d93cbe 100644 --- a/tests/modules/services/emacs/emacs-socket-26.nix +++ b/tests/modules/services/emacs/emacs-socket-28.nix @@ -2,11 +2,13 @@ with lib; -{ +let + +in { config = { nixpkgs.overlays = [ (self: super: rec { - emacs = pkgs.writeShellScriptBin "dummy-emacs-26.3" "" // { + emacs = pkgs.writeShellScriptBin "dummy-emacs-28.0.5" "" // { outPath = "@emacs@"; }; emacsPackagesFor = _: @@ -25,16 +27,16 @@ with lib; assertFileExists home-path/share/applications/emacsclient.desktop assertFileContent home-files/.config/systemd/user/emacs.socket \ - ${./emacs-socket-26-emacs.socket} + ${./emacs-socket-emacs.socket} assertFileContent home-files/.config/systemd/user/emacs.service \ ${ pkgs.substituteAll { inherit (pkgs) runtimeShell; - src = ./emacs-socket-26-emacs.service; + src = ./emacs-socket-emacs.service; } } assertFileContent home-path/share/applications/emacsclient.desktop \ - ${./emacs-emacsclient.desktop} + ${./emacs-28-emacsclient.desktop} ''; }; } diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.service b/tests/modules/services/emacs/emacs-socket-emacs.service similarity index 63% rename from tests/modules/services/emacs/emacs-socket-27-emacs.service rename to tests/modules/services/emacs/emacs-socket-emacs.service index 408a5d24b..52d446cf4 100644 --- a/tests/modules/services/emacs/emacs-socket-27-emacs.service +++ b/tests/modules/services/emacs/emacs-socket-emacs.service @@ -1,9 +1,11 @@ [Service] ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%t/emacs/server'" -ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)' Restart=on-failure +SuccessExitStatus=15 +Type=notify [Unit] -Description=Emacs: the extensible, self-documenting text editor +Description=Emacs text editor Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ +RefuseManualStart=true X-RestartIfChanged=false diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.socket b/tests/modules/services/emacs/emacs-socket-emacs.socket similarity index 76% rename from tests/modules/services/emacs/emacs-socket-27-emacs.socket rename to tests/modules/services/emacs/emacs-socket-emacs.socket index 8fa68bf59..7fffcb0d0 100644 --- a/tests/modules/services/emacs/emacs-socket-27-emacs.socket +++ b/tests/modules/services/emacs/emacs-socket-emacs.socket @@ -8,5 +8,5 @@ ListenStream=%t/emacs/server SocketMode=0600 [Unit] -Description=Emacs: the extensible, self-documenting text editor +Description=Emacs text editor Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/