Tối ưu hóa Workflow: Tích hợp sâu i3 và Emacs với Key Passthrough
Bài viết chia sẻ quá trình tác giả sửa đổi mã nguồn của trình quản lý cửa sổ i3 để tích hợp mượt mà với Emacs. Thay vì dùng script bên ngoài, giải pháp này cho phép chuyển tiếp sự kiện bàn phím trực tiếp, tạo ra bộ phím tắt thống nhất và khả năng quản lý terminal linh hoạt.
Tiling window managers (trình quản lý cửa sổ dạng xếp lát) như i3 và các trình soạn thảo văn bản siêu linh hoạt như Emacs đều là những công cụ tuyệt vời. Trong một thời gian, tôi từng nghĩ EXWM (Emacs X Window Manager) là giải pháp lý tưởng, nhưng thực tế cho thấy tôi vẫn sử dụng các cửa sổ đồ họa thông thường nhiều hơn là các bộ đệm văn bản (text buffers). Đôi khi, những ứng dụng này lại đến từ các phần mềm "khó tính" (ví dụ: Steam) gặp vấn đề với các phương thức nhập liệu phức tạp của EXWM.
Tuy nhiên, tôi vẫn rất yêu thích Emacs. Nó thậm chí còn tự động chuyển đổi chế độ sáng/tối trên máy của tôi. Do đó, được truyền cảm hứng từ các bài viết khác, tôi đã quyết tâm tạo ra một bộ phím tắt chung giữa Emacs và i3, cùng với các thiết lập mặc định hợp lý để mở terminal, chia cửa sổ, v.v.
Vấn đề của các Script bên ngoài
Ban đầu, tôi đã thử viết một script sử dụng xdotool và emacsclient. Nó hoạt động... nhưng lại quá chậm. Tôi quan sát thấy độ trễ lên tới gần một giây từ lúc nhấn phím đến khi hành động được đăng ký. Việc đo thời gian cho thấy độ trễ từ lúc gọi đến lúc thoát script là 30 đến 100 ms. Mặc dù con số này không quá lớn, nhưng trải nghiệm người dùng lại bị ảnh hưởng rõ rệt.
Hơn nữa, việc khởi động cả một shell process chỉ để xử lý một cú nhấn phím dường như rất lãng phí tài nguyên, đặc biệt là với những tổ hợp phím được sử dụng thường xuyên nhất. Vì vậy, tôi đã làm điều duy nhất hợp lý trong tình huống này: vá mã nguồn (patch) của i3.
Giải pháp: Vá i3 để chuyển tiếp phím bấm
Mục tiêu của tôi là thay vì để i3 xử lý đơn phương các lệnh được gán qua bindsym, hãy thêm một tùy chọn để kiểm tra cửa sổ đang được focus. Nếu đó là Emacs, sự kiện bàn phím sẽ được chuyển tiếp trực tiếp cho nó.
Tính năng này từng được yêu cầu trong quá khứ, nhưng những người duy trì i3 đã cho rằng nó nằm ngoài phạm vi (out of scope). Nếu không phải vì điều đó, tôi đã biến bản vá này thành một tính năng hoàn chỉnh hơn. Nếu Emacs quyết định không xử lý phím bấm đó, nó có thể dùng i3-msg để gửi lệnh ngược lại cho i3.
Chi tiết kỹ thuật: Sửa đổi mã nguồn i3
i3 sử dụng xcb_grab_key() với owner_events = 0 trên cửa sổ root để chặn các phím bấm. Điều này có nghĩa là i3 "đánh cắp" hoàn toàn các phím bấm từ bất kỳ ứng dụng nào khác.
Để thực hiện bản vá, tôi đã sửa đổi cấu trúc Binding trong tệp include/data.h, thêm một trường để chỉ định lớp cửa sổ (window class) sẽ nhận sự kiện trực tiếp:
struct Binding {
// ...
struct {
char *class;
} passthrough;
};
Tiếp theo, tôi sửa đổi hàm handle_key_press() trong src/key_press.c. Logic mới như sau: nếu bind->passthrough.class được thiết lập, i3 sẽ lấy cửa sổ đang focus, kiểm tra class của nó. Nếu khớp (ví dụ: "Emacs"), i3 sẽ gửi lại sự kiện phím bấm cho cửa sổ đó bằng xcb_send_event() thay vì thực thi lệnh của chính nó.
void handle_key_press(xcb_key_press_event_t *event) {
// ... (lấy binding)
if (bind->passthrough.class) {
// ... (kiểm tra cửa sổ focus và class)
const bool should_pass = con && con->window->class_class &&
strcmp(con->window->class_class, bind->passthrough.class) == 0;
if (should_pass) {
event->event = focus;
xcb_send_event(conn, false, focus, XCB_EVENT_MASK_NO_EVENT, (const char *)event);
return;
}
}
// Xử lý bình thường nếu không phải Emacs
CommandResult *result = run_binding(bind, NULL);
command_result_free(result);
}
Tôi cũng phải sửa đổi trình phân tích cú pháp (parser) của i3 để hỗ trợ cờ mới --passthrough trong file cấu hình.
Tích hợp phía Emacs
Khi tính năng chuyển tiếp phím (key passthrough) đã hoạt động, chúng ta cần một chút mã Elisp để hoàn thiện trải nghiệm.
Di chuyển cửa sổ
Ý tưởng là khi di chuyển cửa sổ, Emacs sẽ cố gắng chọn một cửa sổ nội bộ theo hướng mong muốn. Nếu không còn cửa sổ nào để chuyển (đã đến biên), nó sẽ gửi lệnh cho i3 để chuyển focus sang cửa sổ khác:
(defun nausicaa/emacs-i3-windmove (dir)
"Chọn cửa sổ theo hướng DIR, nếu không có thì dùng i3 để chuyển."
(let ((other-window (nausicaa/find-other-window dir)))
(if (or (null other-window) (window-minibuffer-p other-window))
(nausicaa/i3-msg "focus" (symbol-name dir))
(nausicaa/do-window-select dir))))
Quản lý Terminal
Tôi thường xuyên mở terminal, đôi khi tới năm mươi cái một ngày. Tôi sử dụng mistty (được tích hợp sâu trong Emacs) cho các tác vụ nhẹ và alacritty cho các tác vụ nặng hơn về đồ họa.
Tôi đã viết các script shell để i3 và Emacs có thể gọi chung một logic. Ví dụ, khi nhấn phím tắt mở terminal:
- Nếu đang ở Emacs: Mở
misttyhoặc gọi script mởalacrittytrong thư mục hiện tại. - Nếu đang ở i3: Gọi script tương ứng.
Cấu hình trong i3 trông như sau:
bindsym --passthrough $super+Return exec mistty-create
bindsym --passthrough $super+Control+Return exec alacritty-create
Nhờ cờ --passthrough, các phím này sẽ được chuyển vào Emacs nếu cửa sổ đang focus là Emacs, cho phép Emacs xử lý việc mở terminal nội bộ hoặc gọi script bên ngoài.
Kết quả
Sau khi áp dụng bản vá và cấu hình, i3 và Emacs hoạt động cùng nhau một cách cực kỳ mượt mà. Bộ phím tắt trở nên thống nhất, và việc chuyển đổi giữa việc soạn thảo code và quản lý cửa sổ hệ thống trở nên liền mạch hơn bao giờ hết. Đây là một ví dụ điển hình cho thấy sức mạnh của việc tùy chỉnh môi trường Linux theo ý muốn.
Nếu bạn quan tâm đến bản vá cho i3 hoặc muốn tìm hiểu thêm về cấu hình chi tiết, bạn có thể tham khảo mã nguồn và các script được tác giả chia sẻ.
Bài viết liên quan

Công nghệ
Cerebras, đối tác thân thiết của OpenAI, sẵn sàng cho đợt IPO kỷ lục định giá tới 26,6 tỷ USD
04 tháng 5, 2026

Công nghệ
Anthropic và OpenAI mang cuộc chiến công nghệ sang chính trường Mỹ
20 tháng 5, 2026

Công nghệ
Cảnh sát bắt giữ nghi can được cho là "ông trùm" của trang web buôn bán ma túy Dream Market
14 tháng 5, 2026
