Trực chỉ nhân tâm - Đi thẳng vô vấn đề

  • Nhớ năm xưa, khi còn vọc i3 khá nhiều, còn thường restart luôn cả window manager. Mình hay bị lost các terminal đang chạy các process cần thiết.

  • Đi làm để máy bật ở công ty qua đêm, về nhà thì cần ssh vô máy ở công ty, cần connect lại các process đang chạy trên terminal (Case này thì dùng vnc cũng được, nhưng mình thích terminal hơn nhiều)

Mình cần một chương trình để giữ các process mỗi khi một terminal được khởi chạy mới.

Tất nhiên là mình đang dùng screentmux cho các session chính mình cần, nhưng trong trường hợp này, mình muốn nó thật đơn giản vì muốn áp dụng cho toàn bộ các terminal được bật.

Chức năng cần thì càng KISS càng tốt:

  • Chỉ cần attach và không cần detach ở trong chính session đó

  • Cũng không cần multi-session.

  • Không một phím tắt nào để tương tác với trình quản lý này

dtach - simple program that emulates the detach feature of screen. đã được mình phát hiện. (Một phát hiện mang tính chiến lược.)

Sử dụng

Mỗi lần bật terminal, mình sẽ bật terminal và chạy dtach luôn, vì thế mà rất trong suốt với người dùng.

Viết 1 cái script như sau:

  • ~/.i3/bin/dtach

#!/usr/bin/env bash
WS=$(i3-msg -t get_workspaces   | jq '.[] | select(.focused==true).num')
urxvt -e dtach -A "/tmp/.${LOGNAME}_term_${WS}_$(date +%s).dtach" -E -z bash -l
------- ChatGPT(modified):

Dòng 1: Gán biến WS bằng kết quả của lệnh "i3-msg -t get_workspaces"
sau đó dùng jq để lọc và chọn workspace đang được focus và lấy số của nó.

Dòng 2: Dùng lệnh urxvt để mở terminal và thực thi lệnh dtach với tùy
chọn "-A" để liên kết vào một session tồn tại (nếu có hoặc tạo mới nếu
chưa tồn tại), tên session được đặt tên theo user (LOGNAME), workspace hiện
tại (WS) và timestamp và đuôi ".dtach".
-E là tắt phím detach.
-z là tắt phím suspend, tức là bấm C-z không có hiệu lực lên dtach, mà lên chương trình chạy bên trong.
Lếnh bash -l để chạy một shell bash login

Tiếp theo là gắn cho phím mở terminal bằng script này

  • ~/.i3/config

 # start a terminal
-bindsym $mod+Return exec i3-sensible-terminal
+bindsym $mod+Return exec ~/.i3/bin/dtach

Vì là mặc định giờ sẽ luôn bật terminal và chạy dtach luôn. Nên sẽ cần 1 cái phím khác để chạy thuần terminal để có thể attach vào các session đã tốn tại. Nên mình thêm 1 config mới

 # start a terminal
 bindsym $mod+Return exec ~/.i3/bin/dtach
+bindsym $mod+Shift+Return exec i3-sensible-terminal

Khi nào thì attach vào

Như bài toán đã đề ra, khi lỡ bị đóng mất cái terminal hoặc cần attach khi ở trong SSH thì rất dễ để attach lại session cũ.

dtach -A /tmp/SESSION_PATH.dtach -E -z bash -l

Đoạn này để tiện cho việc chạy thì viết thêm 1 cái wrapper script như sau:

  • /usr/local/bin/dtacha

#!/usr/bin/env bash
dtach -A $1 -E -z bash -l
  • /etc/bash_completion.d/dtacha

_dtacha(){
    if [ -z "$TMPDIR" ]; then
        export TMPDIR=/tmp
    fi
    local cur prev words cword opts
    _get_comp_words_by_ref -n : cur prev words cword
    COMPREPLY=()
    opts=""
    if [[ ${cword} -eq 1 ]];then
        opts=$(find "$TMPDIR" -name ".*term*" 2>/dev/null)
    fi
    COMPREPLY=( $(compgen -W "$opts" -- "${cur}"))
}
complete -F _dtacha dtacha

Tích hợp với Emacs

Vấn đề tương tự nảy sinh với Emacs, đó là Emacs sẽ crash nếu mình làm việc/phá quá nhiều. Và khi crash ta lại mất các process đang chạy trong các shell của Emacs. Thế là mình viết 1 cái command như sau.

(defun shell-dtach ()
  "Start dtach in shell."
  (interactive)
  (let* ((explicit-shell-file-name "dtach")
         (file-name (concat "/tmp/"
                            (replace-regexp-in-string
                             "/" "~" default-directory) ".dtach"))
         (explicit-dtach-args `("-A" ,file-name "-z"
                                "/bin/bash" "--noediting" "-login")))
    (shell (format "*dtach:%s*" default-directory))))

Thấy việc này có vẻ sẽ hay ho để viết 1 cái gói Emacs để tương tác với dtach. Thật may đúng thời điểm đó đã có 1 người giới thiệu một gói. https://sr.ht/~niklaseklund/detached.el như một định mệnh.

Hm, lúc mới thử thì mình chưa thấy nó match với cái mình cần, hoặc do chưa biết dùng, nên chưa sử dụng. Bẵng đi một thời gian, thì gói này càng phát triển, và còn được release trên https://elpa.gnu.org/packages/ luôn. Mình quay trở lại và dùng thử, ngon rồi đây. Đây cũng chính là triết lý sống Do nothing nằm yên đợi tùy duyên =))

Mình đã config nó vào Emacs của mình TxGVNN/dots:.emacs#detached. Và sau một thời gian mình patch thêm 1 số cái TxGVNN/guxti:guxti/packages/emacs-xyz.scm#L396. Chắc sẽ patch thêm 1 số cái mà mình thấy đang cần, như re-run chưa load lại env từ direnv.

Attach lại trên terminal

Gói detached trên Emacs đã giải quyết bài toán rất ngon, nhưng giờ mình lại cần attach lại các session này (ở Emacs) lên terminal. Thế thì phải viết thêm 1 số mã, sử dụng Emacs làm interface để mà chọn session cần attach.

  • ~/.emacs.d/init.el

(defun detached-attach-session-to-copy-xclip (session)
  "Attach to SESSION."
  (interactive
   (list (detached-session-in-context)))
  (when (and session
             (detached--valid-dtach-executable-p session))
    (let ((initialized-session (detached--get-initialized-session session)))
      (if (detached-session-inactive-p initialized-session)
          (detached-open-session initialized-session))
      (setq command (detached-session-attach-command session :type 'string))
      (xclip-set-selection 'primary command))))

Với detached-attach-session-to-copy-xclip, Emacs sẽ hiện danh sách các session, sau đó thì mình chọn 1 cái. Tiếp đến là copy nó vào trong primary keyboard bởi xclip

  • /usr/local/bin/detached

#!/usr/bin/env bash
EMACS_SOCKET=${EMACS_SOCKET:-"server"}
emacsclient -nw -s $EMACS_SOCKET --eval "(call-interactively 'detached-attach-session-to-copy-xclip)" --eval "(call-interactively 'save-buffers-kill-terminal)"
xdotool key --clearmodifiers Shift+Insert

Triệu hồi chatgpt


------- assistant:

EMACS_SOCKET=${EMACS_SOCKET:-"server"}
# Gán biến EMACS_SOCKET giá trị mặc định là "server" nếu nó chưa được đặt trước đó

emacsclient -nw -s $EMACS_SOCKET --eval "(call-interactively 'detached-attach-session-to-copy-xclip)" --eval "(call-interactively 'save-buffers-kill-terminal)"
# Sử dụng emacsclient để gửi các lệnh tới emacs đã chạy sẵn.
# -nw: Chế độ không hiển thị cửa sổ Emacs
# -s $EMACS_SOCKET: Kết nối đến Emacs qua ổ định socket $EMACS_SOCKET
# --eval "(call-interactively 'detached-attach-session-to-copy-xclip)": Gọi hàm "detached-attach-session-to-copy-xclip" trong Emacs
# --eval "(call-interactively 'save-buffers-kill-terminal)": Gọi hàm "save-buffers-kill-terminal" trong Emacs

xdotool key --clearmodifiers Shift+Insert
# Gửi sự kiện bàn phím "Shift+Insert" bằng công cụ xdotool, với tùy chọn để xóa bỏ các phím modifier hiện tại.

Ghi chú

Do mình không cần phím detach nào trong dtach session(-E Disables the detach character). Nên lúc cần detach thì đơn giản là tìm đến process của dtach đó và gửi signal HUP tới process đó.