Mode démon: différer les invites interactives au démarrage?

16

(Notez que, au contraire, cette question n'est pas la même que Comment démarrer en mode démon et supprimer les dialogues interactifs?, Car cette question a été "répondue" par le demandeur en éliminant ce qui provoquait l'apparition d'une invite particulière.)

Je voudrais savoir s'il existe un moyen général d'éviter emacs --daemonde suspendre indéfiniment une réponse à une invite affichée dans un mini-tampon qui n'existe pas encore.

Il est impossible de se connecter avec un emacsclient pour répondre à ces invites, car le serveur ne démarre pas tant qu'Emacs n'a pas terminé la séquence de démarrage. (Cela signifie que si ALTERNATE_EDITOR est défini sur la chaîne vide, ce qui fait emacsclientqu'un serveur introuvable ne démarre pas un nouveau démon, vous pouvez vous retrouver avec plusieurs démons Emacs tous bloqués et en attente.) Je dois killall emacsrésoudre le problème et le résoudre avant de continuer.

Je peux jouer whack-a-mole avec chaque chose provoquant une invite au démarrage lorsque je l'identifie (en démarrant Emacs en mode non démon et en voyant ce qu'il demande), mais ce n'est pas une solution car il ne peut pas arrêter le prochain démon de se bloquer au démarrage pour une nouvelle raison.

Pour donner un exemple: une raison courante pour laquelle il se bloque était après un redémarrage du système ou un crash Emacs, lorsque le premier Emacs après le redémarrage voulait savoir s'il était correct de voler des fichiers de verrouillage des Emacs disparus. Je pourrais résoudre ce problème en créant des conseils pour que cette invite réponde toujours «oui» sans interaction. Mais ensuite, l'un des fichiers ouverts lors de la sauvegarde de la session précédente était un fichier TRAMP nécessitant un mot de passe sudo ou SSH, donc le démon est coincé en attente d'une invite de mot de passe. Je corrige donc cela en modifiant manuellement le fichier de session (avec viou emacs -q!) Pour supprimer les fichiers incriminés, mais cela ne l'empêche pas de se produire la prochaine fois.

Ainsi, je peux arrêter de charger ma session automatiquement au démarrage et la changer en une commande que je dois exécuter manuellement à partir de mon premier emacsclient. Mais s'il ne charge pas ma session en arrière-plan, il est donc prêt au moment où je suis prêt à l'utiliser, tout le but du démon est perdu!

Donc ce que j'aimerais, c'est:

  • (Meilleur) Un moyen de différer les invites du mini-tampon jusqu'à ce que j'ouvre un emacsclient, tout en terminant le reste de l'initialisation.
  • (OK) Un moyen de faire toutes les invites du nomini-tampon que je n'ai pas déjà conseillé autrement, comme décrit ci-dessus, revenez à moins qu'un emacsclient ne soit en cours d'exécution. Je peux vivre avec mes tampons TRAMP en erreur tant que cela fonctionne principalement.

Existe-t-il un moyen d'atteindre l'un de ces objectifs?

Trey
la source
Existe-t-il un moyen de reproduire ce type de problèmes par programme afin que la communauté puisse résoudre les problèmes?
Melioratus
1
Eh bien, comme je l'ai écrit dans la première ligne, il est assez facile de corriger un exemple donné ... "Docteur, ça fait mal quand je fais ça ..." "Alors ne fais pas ça." Le problème est le cas général. Mais un moyen simple de créer le problème est de faire démarrer le bureau de restauration (read-desktop), puis, avant d'exécuter emacs --daemon, de créer un faux fichier de verrouillage en mettant un entier dans .emacs.desktop.lock (où placer ce fichier, malheureusement, dépend de votre configuration , mais probablement votre homedir ou ~ / .emacs.d / .
Trey
1
C'est un cas fréquemment mentionné ici, par exemple: emacs.stackexchange.com/questions/8147/… ou emacs.stackexchange.com/questions/31621/… peut fournir un contexte.
Trey
Ce bug semble lié: Bug # 13697 - Un moyen de savoir si Emacs peut interagir avec l'utilisateur , mais personne n'a travaillé dessus, à ma connaissance.
npostavs le
@npostavs Merci pour le lien - j'ai annoté le bogue, même s'il a fallu un faux départ que j'ai commenté ici (depuis supprimé) avant de le comprendre!
Trey

Réponses:

2

Notre discussion a révélé que vous n'avez pas de serveur X en cours d'exécution, cela rend ma première solution inutile pour vous.

Dans ce qui suit, je présente une deuxième solution qui fonctionne avec les cadres de terminaux de texte.

Lorsque votre initialisation nécessite une entrée utilisateur via l'une des fonctions recommandées avec avoid-initial-terminalEmacs, attendez que vous ouvriez un cadre de terminal texte. L'invite apparaît dans le mini-tampon de ce cadre et vous pouvez donner votre réponse interactive.

Les informations relatives au code sont fournies sous forme de commentaires dans le code. Il existe des TODOmarqueurs avec des descriptions qui vous indiquent où insérer votre propre configuration. Actuellement, il y a des formulaires de test qui valident le code.

;; 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: version Emacs: 26.1

1er) Exécuter emacs --daemonsur une console.

2ème) Exécuter emacsclient --ttysur une autre console. On vous y demande un mot de passe et une chaîne. Ensuite, vous devez également répondre à une requête y ou np.

Tobias
la source
3

Pas exactement ce que vous demandez, mais peut-être une solution à votre problème d'origine:

Je voudrais savoir s'il existe un moyen général d'empêcher emacs --daemon de se bloquer indéfiniment en attendant une réponse à une invite affichée dans un mini-tampon qui n'existe pas encore.

Si le démon vous donne un cadre graphique pour répondre aux questions qui se posent dans sa phase de démarrage, vous ne restez plus coincé.

Le code ci-dessous définit un conseil général my-with-initial-framequi ouvre un cadre sur le premier écran disponible (par exemple, :0.0).

Ces conseils peuvent être ajoutés facilement aux requêtes de commandes comme y-or-n-pou read-passwd, comme cela est démontré ci-dessous.

Le simple fait d'ouvrir un cadre vous donne une possibilité assez grossière de répondre aux requêtes sur l'interface utilisateur. On pourrait également utiliser une boîte de dialogue pour y-or-n-pmais cela nécessiterait des solutions spéciales pour des commandes d'interrogation spécifiques. Je voulais éviter ça.

Si vous essayez ce code dans votre fichier init, assurez-vous que c'est la première chose.

(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))

Tester:

Hypothèses:

Avoir un xserver en cours d'exécution auquel les programmes peuvent se connecter via la DISPLAYvariable d'environnement.

Entrée à xterm:

emacs --daemon

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

Il ouvre un cadre avec l' y-or-n-pinvite de requête A (y or n). Répondez à cette requête et réessayez:

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

Nouvelle requête avec invite B (y or n)dans le même cadre. Fermez ce cadre, par exemple avec C-x 5 0et réessayez:

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

Un nouveau cadre s'ouvre avec l'invite de requête C (y or n).

La même chose fonctionne pour la saisie de mot de passe.

Tobias
la source
@Trey J'ai eu un problème avec mon code (en fait avec les tests). Le démarrage du x-server n'a pas fonctionné la première fois. Je ne l'ai pas remarqué au départ car je n'ai pas redémarré le démon. J'ai corrigé cela maintenant. Veuillez tester à nouveau. Merci.
Tobias
Mon terminal virtuel Linux n'a pas de terminal graphique attaché, donc je ne peux pas courir xterm. De plus, je ne pense pas que cette solution puisse fonctionner pour ceux qui le font - si vous avez le démon configuré pour s'exécuter au démarrage, il essaiera d'ouvrir un cadre en haut de l'écran de connexion, ce qui n'est pas autorisé, donc il plante.
Trey
Tobias, je m'excuse si ce qui précède sonnait brusque - J'ai répondu sur mon téléphone et je me suis peut-être écourté, alors laissez-moi essayer de développer: le seul avantage que je peux voir de ce que vous décrivez par le fait de ne pas utiliser le démon et de fonctionner server-startà la fin du démarrage au lieu de cela, si vous avez un démarrage propre, vous n'aurez pas à attendre. Mais ... vous devrez attendre, car à moins que je ne comprenne mal, vous ne pouvez pas mettre la tâche pour démarrer le démon Emacs dans votre script de connexion système car une interface graphique ne sera pas disponible à ce moment-là. (Et dans un cas comme le mien, ce ne sera jamais plus tard non plus.)
Trey
@Trey Pourriez-vous vous joindre à un chat ?
Tobias
1

Je pense que reporter les invites va être difficile en général, mais il devrait être assez facile de changer Emacs pour que ces invites signalent immédiatement une erreur.

Non seulement cela, mais si vous ne pouvez pas répondre à ces invites sans beaucoup de gymnastique, je pense que cela peut être considéré comme un bug, donc je vous recommande de soumettre un rapport de bug pour cela.

Stefan
la source
Je pense que j'ai besoin d'un peu plus de détails. Considérez le verrouillage du fichier de sauvegarde du bureau que je mentionne dans le commentaire de la prime ci-dessus. Comment pourrait-on changer l' Warning: desktop file appears to be in use by PID xxx. Using it may cause conflicts. Use it anyway? (y or n)invite en erreur, sans se référer spécifiquement au "bureau" d'une manière ou d'une autre (parce que de cette façon, n'étant pas générique, se trouve un coup fou)?
Trey
Stefan, il y a aussi un problème qui ne me dérange pas parce que je n'exécute pas le démon Emacs de cette façon, mais pour faire une réponse généralement utile, il faudra peut-être s'attaquer: une erreur fatale entraînera le démarrage d'Emacs via systemd ou d'autres chiens de garde. en boucle. Mais ignorer les erreurs et simplement se connecter *Messages*est probablement un avertissement insuffisant lors de la première connexion client, car quelque chose peut sérieusement mal tourner et nécessiter une attention immédiate avant que l'utilisateur ne tente une opération avec état.
Trey
(Pour clarifier pour ceux qui n'utilisent pas le démon - si vous le démarrez manuellement, soit via emacs --daemonou en commençant emacsclientpar la ALTERNATE_EDITORvariable d'environnement définie sur la chaîne vide, vous verrez la sortie qui va normalement en *Messages*écho dans le terminal jusqu'au démon termine l'initialisation et Emacs est prêt. Mais beaucoup ont Emacs démarrer le démon au démarrage du système ou au moment de la connexion, et la sortie est soit enregistrée, soit jetée.
Trey
1
@Trey: la signalisation d'erreur ne devrait pas être dans desktopmais dans la y-or-n-pfonction (ou plus bas encore). Nous avons un mécanisme pour retarder l'affichage des erreurs qui se sont produites au démarrage, nous pourrions donc l'utiliser pour les afficher lorsque le premier emacsclient se connecte au démon.
Stefan
Dans les deux cas, cependant, la plupart des utilisateurs ne lisent pas *Messages*- et tandis que le système peu utilisé *Warnings*ouvre une fenêtre vers le tampon si une trame active existe lorsque l'avertissement est généré, dans ce cas, aucune trame n'existe et elle ne fonctionne pas. ne semble pas facile de différer la fenêtre contextuelle jusqu'au premier emacsclient suivant le problème de l'avertissement. Si cela pouvait être fait, votre suggestion de faire un yes-or-no-pavertissement pré-client serait plutôt idéale. (Je doute que les utilisateurs se peignent *Messages*au démarrage!)
Trey