Daemon-Modus: Interaktive Eingabeaufforderungen beim Start verzögern?

16

(Beachten Sie, dass diese Frage im Gegenteil nicht mit dem Starten im Dämonmodus und Unterdrücken interaktiver Dialoge identisch ist , da diese Frage vom Übermittler "beantwortet" wurde, um zu verhindern, dass eine bestimmte Eingabeaufforderung angezeigt wurde.)

Ich würde gerne wissen, ob es eine allgemeine Möglichkeit gibt, nicht für immer zu emacs --daemonhängen und auf eine Antwort auf eine Eingabeaufforderung zu warten, die in einem noch nicht vorhandenen Minipuffer angezeigt wird.

Es ist unmöglich, eine Verbindung mit einem Emacs-Client herzustellen, um diese Eingabeaufforderungen zu beantworten, da der Server erst gestartet wird, wenn Emacs die Startsequenz beendet hat. (Das bedeutet, wenn Sie ALTERNATE_EDITOR auf den leeren String gesetzt haben, wodurch ein emacsclientServer , der keinen Server findet, einen neuen Daemon starten kann, können mehrere Emacs-Daemons hängen bleiben und warten.) Ich muss killall emacsdas Problem beheben vor dem Fortfahren.

Ich kann mit jedem Ding Schlag auf Schlag spielen, was beim Start eine Aufforderung auslöst, wenn ich es identifiziere (indem ich Emacs im Nicht-Daemon-Modus starte und sehe, wonach es fragt), aber es ist keine Lösung, weil es den nächsten Daemon nicht stoppen kann aus einem neuen Grund beim Start hängen.

Um ein Beispiel zu nennen: Ein häufiger Grund dafür war nach einem Systemneustart oder einem Absturz von Emacs, als Emacs nach dem ersten Neustart wissen wollte, ob es in Ordnung ist, Sperrdateien von dem nicht mehr funktionierenden Emacs zu stehlen. Ich könnte das beheben, indem ich einen Rat gebe, dass diese Eingabeaufforderung immer mit "Ja" ohne Interaktion beantwortet wird. Aber dann war eine der Dateien, die beim Speichern der vorherigen Sitzung geöffnet waren, eine TRAMP-Datei, die ein Sudo- oder SSH-Passwort erfordert, sodass der Daemon nicht mehr auf eine Passwortabfrage wartet. Ich behebe das, indem ich die Sitzungsdatei manuell bearbeite (mit vioder emacs -q!), Um die fehlerhaften Dateien zu entfernen - aber das verhindert nicht, dass es das nächste Mal passiert.

Daher kann ich das automatische Laden meiner Sitzung beim Start stoppen und in einen Befehl ändern, den ich von meinem ersten Emacsclient aus manuell ausführen muss. Wenn meine Sitzung jedoch nicht im Hintergrund geladen wird und zum Zeitpunkt ihrer Verwendung noch nicht fertig ist, geht der gesamte Zweck des Daemons verloren!

Also, was ich möchte, ist:

  • (Best) Eine Möglichkeit, die Eingabeaufforderungen des Minibuffers zu verschieben, bis ich einen Emacsclient öffne, während der Rest der Initialisierung noch abgeschlossen ist.
  • (OK) Auf eine Art und Weise können Sie alle Minibuffer-Eingabeaufforderungen, die ich wie oben beschrieben noch nicht anders empfohlen habe, zurückgeben, es nosei denn, ein Emacsclient wird ausgeführt. Ich kann mit fehlerhaften TRAMP-Puffern leben, solange es größtenteils funktioniert.

Gibt es eine Möglichkeit, eines dieser Ziele zu erreichen?

Trey
quelle
Gibt es eine Möglichkeit, diese Art von Problemen programmgesteuert zu reproduzieren, damit die Community Probleme beheben kann?
Melioratus
1
Nun, wie ich in der ersten Zeile geschrieben habe, ist es ziemlich einfach, ein bestimmtes Beispiel zu korrigieren ... "Doktor, es tut weh, wenn ich das tue ..." "Dann tun Sie das nicht." Das Problem ist der allgemeine Fall. Aber ein einfacher Weg , um das Problem zu schaffen , ist Desktop bis haben Startup wiederherzustellen (read-desktop), dann, vor der Ausführung emacs --daemon, eine gefälschte Sperrdatei erstellen , indem Sie eine ganze Zahl in Putting .emacs.desktop.lock (wo setzen diese Datei leider hängt von Ihrer Konfiguration , aber wahrscheinlich entweder Ihr Homedir oder ~ / .emacs.d / .
Trey
1
Dies ist hier ein häufig genannter Fall, zum Beispiel: emacs.stackexchange.com/questions/8147/… oder emacs.stackexchange.com/questions/31621/… können Kontext bereitstellen.
Trey,
Dieser Fehler scheint im Zusammenhang zu stehen: Fehler # 13697 - Eine Möglichkeit, festzustellen, ob Emacs mit dem Benutzer interagieren kann , aber meines Wissens hat noch niemand daran gearbeitet.
Npostavs
@npostavs Danke für den Link - Ich habe den Fehler kommentiert, obwohl es einen Fehlstart gegeben hat, den ich hier kommentiert (seitdem gelöscht) habe, bevor ich es herausgefunden habe!
Trey

Antworten:

2

Unsere Diskussion hat ergeben, dass Sie keinen X-Server haben, auf dem dies ausgeführt wird. Dies macht meine erste Lösung für Sie nutzlos.

Im Folgenden stelle ich eine zweite Lösung vor, die mit Text-Terminal-Frames funktioniert.

Wenn Ihre Initialisierung Benutzereingaben über eine der in avoid-initial-terminalEmacs empfohlenen Funktionen erfordert, warten Sie, bis Sie einen Textterminalrahmen öffnen. Die Eingabeaufforderung wird im Minipuffer dieses Frames angezeigt, und Sie können Ihre interaktive Antwort geben.

Code-bezogene Informationen werden als Kommentare im Code angegeben. Es gibt TODOMarkierungen mit Beschreibungen, die Ihnen zeigen, wo Sie Ihre eigene Konfiguration einfügen können. Derzeit gibt es dort Testformulare, die den Code validieren.

;; TODO: Do here configure the server if needed.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Startup the server:
;; Analysis of read_from_minibuffer in src/minibuf.c and daemon_type in src/emacs.c
;; shows that daemon-initialized must have run before read-passwd / read-string
;; works on frames. Before it only works on stdin & stdout.
(server-start) ;;< early start
(let ((after-init-time before-init-time))
  (daemon-initialized)) ;; Finalize the daemon, 

(advice-add 'daemon-initialized :override #'ignore)
;;< Ignore `daemon-initialized' after initialization. It may only run once!
;; Now the background emacs is no longer marked as daemon. It just runs the server.

(defun prevent-server-start (&rest _ignore)
  "Prevent starting a server one time after `server-start' has been advised with this."
  (advice-remove 'server-start #'prevent-server-start))

(advice-add 'server-start :override #'prevent-server-start)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Prepare waiting for a real terminal frame when user input is required:

(defun avoid-initial-terminal (fun &rest args)
  "Wait until we are no longer on \"intial-terminal\".
Afterwards run `fun' with frame on the other terminal selected."
  (message "Avoiding initial terminal. Terminal: %S" (get-device-terminal nil))
  (while (string-equal
      (terminal-name (get-device-terminal nil))
      "initial_terminal")
    (sleep-for 1))
  ;; (message "Selected frame: %S; Running %S with %S." (selected-frame) fun args)
  (apply fun args))

(advice-add 'read-string :around #'avoid-initial-terminal)

(advice-add 'read-passwd :around #'avoid-initial-terminal)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: Your initialization that is not daemon related
;; and may require user input:

;; Currently this is just a test.
(read-passwd "Passwd: ")

(read-string "String: ")

(y-or-n-p "y-or-n query")

Test: Emacs-Version: 26.1

1.) emacs --daemonAuf einer Konsole ausführen.

2.) emacsclient --ttyAuf einer anderen Konsole ausführen. Sie werden dort nach einem Passwort und einer Zeichenkette gefragt. Anschließend müssen Sie auch eine y-or-np-Anfrage beantworten.

Tobias
quelle
3

Nicht genau das, wonach Sie fragen, aber vielleicht eine Lösung für Ihr ursprüngliches Problem:

Ich würde gerne wissen, ob es eine allgemeine Möglichkeit gibt, zu verhindern, dass emacs --daemon für immer hängt und auf eine Antwort auf eine Eingabeaufforderung wartet, die in einem noch nicht vorhandenen Minipuffer angezeigt wird.

Wenn der Daemon Ihnen einen grafischen Rahmen für die Beantwortung von Fragen in der Startphase bietet, bleiben Sie nicht mehr hängen.

Der folgende Code definiert einen allgemeinen Hinweis my-with-initial-frame, der einen Rahmen auf der ersten verfügbaren Anzeige öffnet (z :0.0. B. ).

Dieser Rat kann leicht zum Abfragen von Befehlen wie y-or-n-poder hinzugefügt werden, wie read-passwdunten gezeigt.

Durch einfaches Öffnen eines Frames haben Sie eine ziemlich grobe Möglichkeit, die Fragen auf der Benutzeroberfläche zu beantworten. Man könnte auch ein Dialogfeld für verwenden, y-or-n-paber das würde spezielle Lösungen für bestimmte Abfragebefehle erfordern. Das wollte ich vermeiden.

Wenn Sie diesen Code in Ihrer Init-Datei ausprobieren, stellen Sie sicher, dass er das erste ist, was dort vorkommt.

(when (daemonp)

  (defun my-with-initial-frame (&rest _)
    "Ensure a frame on display :0.0 and ignore args."
    (let* ((display-list (x-display-list))
           (display-re (and display-list (regexp-opt display-list)))
           (term (and display-re (cl-some (lambda (term) (and (string-match display-re (terminal-name term)) term)) (terminal-list))))
           (frame (and term (cl-some (lambda (frame) (and (frame-live-p frame) frame)) (frames-on-display-list term)))))
      (select-frame (or frame (make-frame-on-display (getenv "DISPLAY"))))))

  (message "Advising querying functions with `my-with-initial-frame'.")
  (advice-add 'y-or-n-p :before #'my-with-initial-frame)
  (advice-add 'read-passwd :before #'my-with-initial-frame))

Prüfung:

Annahmen:

Habe einen laufenden xserver, mit dem sich Programme über die DISPLAYUmgebungsvariable verbinden können.

Eingabe bei xterm:

emacs --daemon

emacsclient --eval '(y-or-n-p "A")'

Es öffnet sich ein Rahmen mit der y-or-n-pAbfrageeingabeaufforderung A (y or n). Beantworten Sie diese Frage und versuchen Sie es erneut:

emacsclient --eval '(y-or-n-p "B")'

Neue Abfrage mit Eingabeaufforderung B (y or n)im selben Frame. Schließen Sie diesen Rahmen zB mit C-x 5 0und versuchen Sie es erneut:

emacsclient --eval '(y-or-n-p "C")'

Ein neuer Frame wird mit der Abfrageeingabeaufforderung geöffnet C (y or n).

Gleiches gilt für die Passworteingabe.

Tobias
quelle
@Trey Ich hatte ein Problem mit meinem Code (eigentlich beim Testen). Das Starten des X-Servers hat beim ersten Mal nicht funktioniert. Ich habe es anfangs nicht bemerkt, da ich den Daemon nicht neu gestartet habe. Ich habe das jetzt korrigiert. Bitte nochmal testen. Vielen Dank.
Tobias
Mein virtuelles Linux hat kein Grafikterminal, daher kann ich nicht ausgeführt werden xterm. Ich glaube auch nicht, dass diese Lösung für diejenigen geeignet ist, die dies tun. Wenn Sie den Daemon so eingerichtet haben, dass er beim Start ausgeführt wird, wird versucht, einen Frame über dem Anmeldebildschirm zu öffnen, was nicht zulässig ist stürzt ab.
Trey
Tobias, entschuldige dich, wenn sich das oben Gesagte brüsk anhört - ich habe auf meinem Handy geantwortet und mich möglicherweise abgekürzt. Lassen Sie mich das näher erläutern: Der einzige Vorteil, den ich sehen kann, ist, dass Sie den Daemon nicht verwenden und server-startam Ende des Startvorgangs ausgeführt werden Stattdessen müssen Sie nicht warten, wenn Sie einen sauberen Start haben. Aber ... Sie müssen warten, denn wenn ich nichts falsch verstehe, können Sie die Aufgabe zum Starten des Emacs-Daemons nicht in Ihr Systemanmeldeskript aufnehmen, da dann keine GUI verfügbar ist. (Und in einem Fall wie meinem wird es auch nie später sein.)
Trey,
@Trey Könnten Sie an einem Chat teilnehmen ?
Tobias
1

Ich denke , dass es im Allgemeinen schwierig sein wird , die Eingabeaufforderungen aufzuschieben , aber es sollte ziemlich einfach sein, Emacs so zu ändern, dass solche Eingabeaufforderungen sofort einen Fehler signalisieren.

Und wenn Sie diese Aufforderungen nicht ohne viel Gymnastik beantworten können, ist dies meines Erachtens ein Fehler. Ich empfehle Ihnen daher, einen Fehlerbericht einzureichen.

Stefan
quelle
Ich glaube, ich brauche ein bisschen mehr Details. Betrachten Sie die Desktop-Sperre für das Speichern von Dateien, die ich im Kommentar zur obigen Prämie erwähnt habe. Wie kann man die Warning: desktop file appears to be in use by PID xxx. Using it may cause conflicts. Use it anyway? (y or n)Eingabeaufforderung in einen Fehler umwandeln, ohne auf irgendeine Weise speziell auf "Desktop" Bezug zu nehmen (weil auf diese Weise, da dies nicht generell ist, alles andere als einfach ist)?
Trey
Stefan, es gibt auch ein Problem, das mich nicht stört , weil ich den Emacs-Daemon nicht auf diese Weise ausführe. Um jedoch eine allgemein nützliche Antwort zu geben, muss möglicherweise Folgendes behoben werden: Wenn ein Fehler tödlich ist, wird Emacs über systemd oder andere Watchdogs neu gestartet in einer Schleife. Das Ignorieren von Fehlern und das bloße Anmelden bei reicht *Messages*jedoch wahrscheinlich nicht aus, um beim Herstellen einer Verbindung zum ersten Client sofortige Aufmerksamkeit zu erregen, bevor der Benutzer zustandsbehaftete Vorgänge ausführt.
Trey,
(Zur Verdeutlichung: Wenn Sie den Dämon manuell starten, entweder über emacs --daemonoder indem Sie emacsclientmit der ALTERNATE_EDITORUmgebungsvariablen beginnen, die auf die leere Zeichenfolge festgelegt ist, wird die Ausgabe angezeigt, die normalerweise *Messages*im Terminal bis zum Dämon wiedergegeben wird Die Initialisierung ist abgeschlossen, und Emacs ist bereit. In vielen Fällen wird der Daemon jedoch beim Systemstart oder bei der Anmeldung gestartet, und die Ausgabe wird entweder protokolliert oder verworfen.
Trey,
1
@Trey: Die Fehlersignalisierung sollte nicht in desktopsondern in der y-or-n-pFunktion (oder noch tiefer) sein. Wir haben einige Mechanismen, um die Anzeige von Fehlern zu verzögern, die während des Startvorgangs aufgetreten sind, sodass wir diese verwenden können, um sie anzuzeigen, wenn der erste Emacsclient eine Verbindung zum Dämon herstellt.
Stefan
In beiden Fällen lesen die meisten Benutzer jedoch nicht - *Messages*und während das wenig genutzte *Warnings*System ein Fenster zum Puffer öffnet, wenn ein aktiver Frame vorhanden ist, wenn die Warnung generiert wird, ist in diesem Fall kein Frame vorhanden, und dies ist nicht der Fall Es scheint nicht einfach zu sein, das Popup bis zum ersten Emacsclient nach dem Problem der Warnung aufzuschieben. Wenn dies möglich wäre, wäre Ihr Vorschlag yes-or-no-p, stattdessen eine Vor-Client- Warnung zu erstellen, ideal. (Ich bezweifle, dass Benutzer *Messages*beim Start kämmen !)
Trey