Mode Daemon: Menunda permintaan interaktif saat memulai?

16

(Perhatikan bahwa, sebaliknya, pertanyaan ini tidak sama dengan Bagaimana memulai dalam mode daemon dan menekan dialog interaktif?, Karena pertanyaan itu "dijawab" oleh submitter yang menghilangkan apa yang menyebabkan prompt tertentu muncul.)

Saya ingin tahu apakah ada cara umum untuk tidak emacs --daemonmenggantung selamanya menunggu jawaban prompt yang ditampilkan di minibuffer yang belum ada.

Mustahil untuk terhubung dengan emacsclient untuk menjawab prompt ini, karena server tidak memulai sampai Emacs menyelesaikan urutan startup. (Ini berarti, jika Anda memiliki ALTERNATE_EDITOR yang disetel ke string kosong, yang membuat emacsclientserver yang tidak dapat menemukan memulai daemon baru, Anda dapat berakhir dengan beberapa daemon Emacs yang semuanya macet dan menunggu.) Saya harus killall emacsmemperbaiki masalahnya sebelum melanjutkan.

Saya dapat memainkan whack-a-mole dengan setiap hal menyebabkan prompt pada startup ketika saya mengidentifikasinya (dengan memulai Emacs dalam mode non-daemon dan melihat apa yang ditanyakan), tetapi itu bukan solusi karena tidak dapat menghentikan daemon berikutnya dari menggantung di startup karena alasan baru.

Untuk memberikan contoh: alasan umum itu akan hang adalah setelah sistem reboot atau crash Emacs, ketika Emacs pasca-reboot pertama ingin tahu apakah itu boleh saja mencuri lockfiles dari Emacs yang sudah tidak berfungsi. Saya bisa memperbaikinya dengan membuat saran agar prompt itu selalu menjawab "ya" tanpa interaksi. Tapi kemudian, salah satu file yang terbuka di sesi sebelumnya simpan adalah file TRAMP yang membutuhkan sudo atau kata sandi SSH, sehingga daemon macet menunggu pada prompt kata sandi. Jadi saya memperbaikinya dengan mengedit file sesi secara manual (dengan viatau emacs -q!) Untuk menghapus file yang menyinggung — tetapi itu tidak mencegahnya terjadi di waktu berikutnya.

Jadi, saya bisa berhenti memuat sesi saya secara otomatis saat startup dan mengubahnya menjadi perintah yang harus saya jalankan secara manual dari emacsclient pertama saya. Tetapi jika itu tidak memuat sesi saya di latar belakang sehingga siap pada saat saya siap menggunakannya, seluruh tujuan daemon hilang!

Jadi yang saya suka adalah:

  • (Terbaik) Beberapa cara untuk menunda minibuffer meminta hingga saya membuka emacsclient, sambil tetap menyelesaikan sisa inisialisasi.
  • (OK) Beberapa cara untuk membuat semua minibuffer meminta saya belum menyarankan sebaliknya seperti yang dijelaskan di atas hanya kembali nokecuali emacsclient sedang berjalan. Saya dapat hidup dengan buffer TRAMP saya yang gagal asalkan sebagian besar berfungsi.

Apakah ada cara untuk mencapai salah satu dari tujuan ini?

Angka tiga
sumber
Apakah ada cara untuk mereproduksi jenis masalah ini secara terprogram sehingga komunitas dapat memecahkan masalah?
Melioratus
1
Nah, seperti yang saya tulis di baris pertama, cukup mudah untuk memperbaiki contoh yang diberikan ... "Dokter, sakit ketika saya melakukan ini ..." "Kalau begitu jangan lakukan itu." Masalahnya adalah kasus umum. Tetapi cara sederhana untuk membuat masalah adalah memiliki startup mengembalikan desktop melalui (read-desktop), kemudian, sebelum menjalankan emacs --daemon, membuat file kunci palsu dengan meletakkan integer ke .emacs.desktop.lock (di mana meletakkan file itu, sayangnya, tergantung pada konfigurasi Anda , tapi mungkin homedir Anda atau ~ / .emacs.d / .
Trey
1
Ini adalah kasus yang sering disebutkan di sini, misalnya: emacs.stackexchange.com/questions/8147/… atau emacs.stackexchange.com/questions/31621/… dapat memberikan konteks.
Trey
Bug ini tampaknya terkait: Bug # 13697 - Cara untuk mengetahui apakah Emacs dapat berinteraksi dengan pengguna , tetapi tidak ada yang berhasil, sejauh yang saya tahu.
npostavs
@npostavs Terima kasih atas tautannya — Saya telah membuat anotasi bug, meskipun butuh awal yang salah yang saya komentari di sini (sejak dihapus) sebelum saya menemukan jawabannya!
Trey

Jawaban:

2

Diskusi kami menyatakan bahwa Anda tidak memiliki X-server yang menjalankan ini membuat solusi pertama saya tidak berguna untuk Anda.

Berikut ini saya sajikan solusi kedua yang bekerja dengan bingkai terminal teks.

Ketika inisialisasi Anda memerlukan input pengguna melalui salah satu fungsi yang disarankan dengan avoid-initial-terminalEmacs menunggu hingga Anda membuka bingkai terminal teks. Prompt muncul di minibuffer frame itu dan Anda dapat memberikan respons interaktif Anda.

Informasi terkait kode diberikan sebagai komentar dalam kode. Ada TODOspidol dengan deskripsi yang menunjukkan tempat untuk memasukkan konfigurasi Anda sendiri. Saat ini ada formulir tes di sana yang memvalidasi kode.

;; 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")

Uji: Emacs-versi: 26.1

1) Jalankan emacs --daemondi konsol.

2) Jalankan emacsclient --ttydi konsol lain. Anda diminta kata sandi dan sebuah string. Setelah itu Anda juga diminta untuk menjawab kueri y-atau-np.

Tobias
sumber
3

Tidak persis apa yang Anda minta, tetapi mungkin solusi untuk masalah awal Anda:

Saya ingin tahu apakah ada cara umum untuk menjaga emacs --daemon agar tidak menggantung selamanya menunggu jawaban prompt yang ditampilkan di minibuffer yang belum ada.

Jika daemon memberi Anda bingkai grafis untuk menjawab pertanyaan yang muncul pada fase startup Anda tidak terjebak lagi.

Kode di bawah ini mendefinisikan saran umum my-with-initial-frameyang membuka bingkai pada tampilan pertama yang tersedia (misalnya, :0.0).

Saran itu dapat ditambahkan dengan mudah ke perintah kueri seperti y-or-n-patau read-passwd, seperti yang ditunjukkan di bawah ini.

Hanya dengan membuka bingkai memberi Anda kemungkinan yang agak kasar untuk menjawab pertanyaan pada antarmuka pengguna. Orang juga bisa menggunakan kotak dialog untuk y-or-n-ptetapi itu akan membutuhkan solusi khusus untuk perintah permintaan tertentu. Saya ingin menghindarinya.

Jika Anda mencoba kode itu di file init Anda, pastikan itu adalah hal pertama di sana.

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

Uji:

Asumsi:

Memiliki xserver yang sedang berjalan yang dapat dihubungkan oleh program melalui DISPLAYvariabel lingkungan.

Masukan di xterm:

emacs --daemon

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

Ada membuka bingkai dengan y-or-n-pprompt permintaan A (y or n). Jawab kueri itu dan coba lagi:

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

Kueri baru dengan prompt B (y or n)di bingkai yang sama. Tutup frame itu, misalnya dengan C-x 5 0dan coba lagi:

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

Frame baru terbuka dengan prompt permintaan C (y or n).

Hal yang sama berlaku untuk input kata sandi.

Tobias
sumber
@ Andy aku punya masalah dengan kode saya (sebenarnya dengan pengujian). Memulai x-server tidak bekerja pertama kali. Awalnya saya tidak memperhatikan karena saya tidak memulai ulang daemon. Saya memperbaikinya sekarang. Silakan tes lagi. Terima kasih.
Tobias
Linux virtual saya tidak memiliki terminal grafis yang terpasang, jadi saya tidak dapat menjalankannya xterm. Juga, saya tidak berpikir solusi ini dapat bekerja untuk mereka yang melakukannya - jika Anda memiliki daemon yang diatur untuk dijalankan saat startup, itu akan mencoba untuk membuka bingkai di atas layar login, yang tidak diperbolehkan, jadi itu adalah crash.
Trey
Tobias, minta maaf jika jawaban di atas terdengar kasar— Aku menjawab di ponselku dan mungkin telah memotong diriku sendiri, jadi izinkan aku mencoba menguraikan: satu-satunya keuntungan yang bisa kulihat dari apa yang kau jelaskan adalah tidak menggunakan daemon dan berlari server-startdi akhir startup sebaliknya, jika Anda memiliki startup yang bersih, Anda tidak perlu menunggu. Tapi ... Anda harus menunggu, karena kecuali saya salah paham, Anda tidak dapat menempatkan tugas untuk memulai daemon Emacs ke skrip login sistem Anda karena GUI tidak akan tersedia saat itu. (Dan dalam kasus seperti milik saya, tidak akan pernah nanti, juga.)
Trey
@Trey Bisakah Anda bergabung dalam obrolan ?
Tobias
1

Saya pikir menunda permintaan akan sulit secara umum, tetapi harus cukup mudah untuk mengubah Emacs sehingga permintaan tersebut segera memberi sinyal kesalahan.

Tidak hanya itu, tetapi jika Anda tidak dapat menjawab permintaan tersebut tanpa banyak senam, saya pikir itu memenuhi syarat sebagai bug, jadi saya sarankan Anda mengirimkan laporan bug untuk itu.

Stefan
sumber
Saya pikir saya perlu sedikit lebih detail. Pertimbangkan kunci penyimpanan file desktop yang saya sebutkan di komentar untuk karunia di atas. Bagaimana cara mengubah Warning: desktop file appears to be in use by PID xxx. Using it may cause conflicts. Use it anyway? (y or n)prompt menjadi kesalahan, tanpa secara khusus merujuk ke "desktop" dalam beberapa cara (karena dengan cara itu, menjadi nongeneral, terletak mendera)?
Trey
Stefan, juga ada masalah yang tidak mengganggu saya karena saya tidak menjalankan daemon Emacs dengan cara ini, tetapi untuk membuat jawaban yang umumnya berguna mungkin perlu diatasi: kesalahan fatal akan menyebabkan Emac memulai melalui systemd atau pengawas lainnya untuk memulai kembali dalam satu lingkaran. Tetapi mengabaikan kesalahan dan hanya masuk ke *Messages*mungkin tidak cukup head-up pada klien pertama terhubung bahwa sesuatu mungkin serius dan perlu perhatian segera sebelum pengguna mencoba operasi stateful.
Trey
(Untuk memperjelas bagi mereka yang tidak menggunakan daemon — jika Anda memulainya secara manual, baik melalui emacs --daemonatau dengan memulai emacsclientdengan ALTERNATE_EDITORvariabel lingkungan yang disetel ke string kosong, Anda akan melihat output yang biasanya masuk ke *Messages*gema di terminal hingga daemon menyelesaikan inisialisasi dan Emacs siap.Tapi banyak Emacs memulai daemon pada saat startup sistem atau waktu login, dan hasilnya dicatat atau dibuang
Trey
1
@ Thomas: pensinyalan kesalahan seharusnya tidak ada di dalam desktoptetapi dalam y-or-n-pfungsi (atau lebih rendah lagi). Kami memiliki beberapa mekanisme untuk menunda menampilkan kesalahan yang terjadi selama startup, jadi kami bisa menggunakannya untuk menampilkannya ketika emacsclient pertama terhubung ke daemon.
Stefan
Namun dalam kedua kasus tersebut, sebagian besar pengguna tidak membaca dengan teliti *Messages*— dan sementara sistem yang jarang digunakan *Warnings*itu memunculkan jendela ke buffer jika bingkai aktif ada ketika peringatan dihasilkan, dalam hal ini, tidak ada bingkai yang ada, dan itu tidak Tampaknya mudah untuk menunda pop-up hingga emacsclient pertama setelah masalah peringatan. Jika itu bisa dilakukan, saran Anda untuk membuat yes-or-no-pperingatan pra-klien sebagai gantinya akan sangat ideal. (Saya ragu pengguna menyisir *Messages*startup!)
Trey