1 điểm bởi GN⁺ 2025-08-16 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nhóm Ghostty đã viết lại hoàn toàn ứng dụng GTK và tích cực tận dụng hệ thống kiểu GObject
  • Trong quá trình này, việc tích hợp với ngôn ngữ Zig và kiểm tra vấn đề bộ nhớ bằng Valgrind đóng vai trò quan trọng
  • Việc áp dụng hệ thống GObject giúp quản lý bộ nhớtriển khai widget tùy chỉnh trở nên đơn giản hơn trước
  • Kết quả từ việc sử dụng Valgrind cho thấy độ an toàn bộ nhớ của Ghostty đã được cải thiện đáng kể
  • Ghostty GTK mới đã trở thành mặc định cho các bản dựng từ mã nguồn và sẽ được đưa vào bản phát hành 1.2

Giới thiệu

  • Ghostty là một trình giả lập terminal đa nền tảng hỗ trợ macOS, Linux, FreeBSD
  • Mỗi nền tảng sử dụng framework GUI native riêng để tạo khác biệt
    • macOS: ứng dụng quy mô lớn dựa trên Swift và Xcode
    • Linux và BSD: ứng dụng dựa trên GTK, tích hợp trực tiếp với X11/Wayland
    • Phần lõi dùng chung được viết bằng Zig và cung cấp API tương thích C ABI
  • Có thể tham khảo PR gốc để biết lý do viết lại ứng dụng GTK trong cấu trúc cũ
  • Bài viết này tập trung vào việc tích hợp với hệ thống kiểu GObjectcác vấn đề bộ nhớ được xác minh bằng Valgrind

Hệ thống kiểu GObject và Zig

  • Khi sử dụng GTK, về cơ bản phải tương tác với hệ thống kiểu GObject
  • Trước đây, dự án cố tránh dùng hệ thống GObject và tự đồng bộ vòng đời của đối tượng Zig không có reference counting với đối tượng GObject, nhưng liên tục gặp vấn đề giải phóng bộ nhớ không đúng cách
    • Ví dụ: bộ nhớ phía Zig đã được giải phóng nhưng bộ nhớ phía GTK vẫn còn tồn tại, hoặc ngược lại
  • Cách tiếp cận này không chỉ gặp vấn đề về tính đúng đắn mà còn khiến việc sử dụng các tính năng đặc thù của GTK (event signal, property binding, action) trở nên khó khăn
  • Một ví dụ cụ thể là khi reload struct cấu hình (config), mọi phần tử GUI liên quan đều phải được cập nhật nhất quán, nhưng quá trình này phức tạp và dễ lỗi
    • Hiện tại, dự án quản lý nó bằng GhosttyConfig GObject có reference counting bao bọc struct Config của Zig, và các thông báo thay đổi thuộc tính giúp thay đổi được lan truyền tự nhiên trong toàn bộ ứng dụng
  • Việc tạo widget GObject tùy chỉnh cũng trở nên dễ dàng hơn, cho phép dùng các công nghệ UI GTK hiện đại như Blueprint
    • Gần đây, nhờ đưa vào Blueprint, việc bổ sung các tính năng mới như tab trên thanh tiêu đề GTKviền chuông có hiệu ứng động đã trở nên dễ dàng hơn

Valgrind với GTK và Zig

  • Trong toàn bộ quá trình phát triển, nhóm đã dùng Valgrind để kiểm chứng một cách có hệ thống các vấn đề như rò rỉ bộ nhớ và truy cập bộ nhớ chưa được định nghĩa
  • Việc kiểm tra ứng dụng GTK bằng Valgrind khá khó khăn và cần tới các tệp suppression dung lượng lớn (80% là từ chính GTK, phần còn lại là thư viện bên thứ ba và driver GPU)
  • Nhờ kiểm tra lặp đi lặp lại, nhóm có thể phát hiện sớm những lỗi bộ nhớ phức tạp chỉ xuất hiện trong một số trường hợp nhất định
    • Ví dụ: nếu không khởi tạo đúng GObject WeakRef, khi đối tượng đích được giải phóng về sau sẽ xảy ra truy cập bộ nhớ chưa được định nghĩa, và Valgrind đã phát hiện điều này từ trước
  • Trong trải nghiệm thực tế, bên trong codebase Zig chỉ có tổng cộng 2 vấn đề (1 rò rỉ, 1 truy cập chưa được định nghĩa), và cả hai đều phát sinh trong quá trình tích hợp với C API của bên thứ ba
    • Allocator debug của Zig và khả năng tích hợp với Valgrind cũng đã chứng minh được hiệu quả thực tế
  • Các vấn đề bộ nhớ khác được phát hiện hầu hết xuất phát từ ranh giới C API và việc quản lý vòng đời phức tạp của hệ thống GObject
    • Kết luận là, để sử dụng an toàn C API của các thư viện phức tạp, cần những công cụ như Valgrind
  • Các tính năng hỗ trợ an toàn bộ nhớ của Zig không chỉ hiệu quả trên lý thuyết mà còn được xác nhận qua trải nghiệm dự án thực tế

Kết luận

  • Đây là lần thứ năm phần GUI của Ghostty được xây lại từ đầu
    • Theo thứ tự: GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (thủ tục), Linux GTK + hệ thống kiểu GObject
  • Qua mỗi lần viết lại lặp đi lặp lại, nhóm đều thu được bài học mới và sự trưởng thành về kỹ thuật
    • Họ cũng có kế hoạch áp dụng một phần kinh nghiệm này cho dự án macOS
  • Bài viết cũng nhấn mạnh sự hợp tác tích cực từ đội duy trì hệ thống Ghostty GTK
  • Ứng dụng Ghostty GTK được viết lại nay đã trở thành mặc định cho các bản dựng từ mã nguồn và sẽ được áp dụng trong bản phát hành chính thức 1.2

1 bình luận

 
GN⁺ 2025-08-16
Ý kiến trên Hacker News
  • Tôi chưa từng làm việc trực tiếp với GTK, nhưng nghe mô tả thì cảm giác rất giống những vấn đề tôi gặp khi làm binding Godot bằng Zig. Godot có cực nhiều khái niệm OOP như class, virtual method, property, signal, v.v. Và nó cung cấp API C để xử lý toàn bộ các khái niệm đó cũng như cho phép tạo đối tượng và thuộc tính do người dùng định nghĩa. Nó tự quản lý vòng đời của các đối tượng trong engine, và còn có cả cấu trúc cây của các đối tượng dùng reference counting. Khi cố gắng gói các vấn đề về vòng đời này thành một API tối ưu, phù hợp với lối viết Zig, mọi thứ trở nên cực kỳ phức tạp. Trong lúc vật lộn với chuyện đó, tôi còn tạo ra cả thư viện oopz. API hiện vẫn ở mức này, và ví dụ thực tế có thể xem ở đây. Tôi cũng muốn thử làm frontend Ghostty dưới dạng Godot extension

    • Trước đây tôi từng dùng trực tiếp GTK binding cho ngôn ngữ của mình và nhớ là khá bất tiện. 98% thì ổn, nhưng ở 2% còn lại có những chỗ kiểu như “hàm này có nhận tham chiếu tới đối tượng hay không lại phụ thuộc vào tham số khác”, nên việc phân tích vòng đời đối tượng khá đau đầu
    • Xin gửi lời cảm ơn, đồng thời tôi nhớ đã từng rất khổ khi cố viết code Godot bằng C# sao cho hiệu năng tốt, vì phải chuyển đổi qua lại với các kiểu của engine quá nhiều, dẫn đến phát sinh cấp phát lặp đi lặp lại. Tôi tò mò không biết khi làm binding bạn có gặp vấn đề này không
    • Tôi không biết là đang có dự án làm Godot binding bằng Zig. Tôi rất thích cả Godot lẫn Zig nên khá háo hức. Tôi sẽ tiếp tục theo dõi
  • Đây là ví dụ cho thấy lập trình tốt rốt cuộc là phải đi theo cách mà hệ thống cung cấp. Dù bạn nghĩ gì về OOP hay quản lý bộ nhớ, nếu dùng GTK thì bằng cách nào đó bạn vẫn phải thiết kế interface với hệ thống kiểu GObject. Muốn tránh cũng không tránh được. Nhưng chúng tôi đã cố tránh, và kết quả là một mớ hỗn loạn khổng lồ khi ràng buộc vòng đời của các đối tượng có reference counting với các đối tượng không có. Trong ứng dụng Ghostty GTK, lỗi kiểu giải phóng bộ nhớ Zig thì bộ nhớ GTK không được giải phóng, hoặc ngược lại, cứ lặp đi lặp lại

    • Bối cảnh ra đời của Vala chính là vì GTK có cấu trúc như vậy. Vala lấy cảm hứng từ C#, tận dụng GObject, rồi transpile mã sang C. Vì vậy có khá nhiều ứng dụng GTK thực ra được viết bằng Vala. Đôi lúc tôi thấy tiếc vì sao không phải là ngôn ngữ D. D ở nhiều khía cạnh cho cảm giác như một thứ C# biên dịch được
    • Khuất phục trước một hệ thống tệ không phải là điều tốt đẹp, mà là một lựa chọn thực dụng
  • Bỏ qua quan điểm của tôi về OOP và quản lý bộ nhớ, tôi đồng ý rằng nếu dùng GTK thì không thể tránh bị cuốn vào hệ thống kiểu GObject. Vì vậy tôi quyết định không dùng GTK trực tiếp ngay từ đầu. Tôi hiểu giá trị của một giao diện có theme thống nhất, nhưng theo tôi thì các ưu điểm của GTK không đủ lớn để đáng phải trả cái giá đó. Từ kinh nghiệm từng đụng vào vùng ngoại vi của GTK trong các ứng dụng mã nguồn mở, tôi tin chắc rằng quan điểm của GTK và GObject không hợp với cách làm của mình. Tôi không ghét việc GTK tồn tại. Tôi chọn không dùng nó và thế là ổn, nhưng điều kỳ lạ là có người lại không xem đó là quyền lựa chọn của tôi. Nó chỉ là một trong vô số GUI toolkit, và dù xét về kỹ thuật thì đây là một toolkit rất trau chuốt, tôi vẫn tự hỏi nếu GTK có thị phần thấp hơn một chút thì biết đâu sự polish đó đã được đổ vào những toolkit khác có cấu trúc tốt hơn. Tất nhiên, thứ tôi cho là tốt không có nghĩa là tốt với tất cả mọi người. Tôi tò mò không biết trong số những người dùng GTK, có bao nhiêu người dùng miễn cưỡng và bao nhiêu người thực sự thấy đó là toolkit tốt nhất

    • Tôi đồng ý với ý rằng phong cách nhiều tính áp đặt của GTK và GObject cũng không hợp với suy nghĩ của tôi. Tôi cũng thấy mình lệch khá xa với định hướng của hệ sinh thái Gnome. Dùng GTK cho Ghostty trên Linux là một lựa chọn rất thực dụng. Mục tiêu “native theo nền tảng”, đặc biệt trên Linux, của Ghostty được định nghĩa ở đây. GTK là thứ được dùng rộng rãi nhất trên Linux và hòa nhập tự nhiên nhất với phần lớn hệ sinh thái ứng dụng, nên gần như buộc phải đưa ra quyết định đó. Về sau tôi hy vọng libghostty sẽ có nhiều frontend đa dạng do bên thứ ba phát triển. Ví dụ đã có Wraith, một frontend Ghostty native cho Wayland. Rất ngầu
    • Tôi nghĩ lý do cốt lõi khiến GTK được dùng rộng rãi trên Linux chính là vì nó có “C binding”. Nhờ vậy gần như ngôn ngữ nào cũng có binding mặc định hoặc rất dễ tự động sinh ra. Trong khi đó, Qt lại bị gắn quá chặt với C++ và Python nên mức độ tiếp cận giảm hẳn. Điều quan trọng là phải đáp ứng được nhà phát triển ở chính ngôn ngữ mà họ đang dùng. Hơn nữa, khi viết ứng dụng desktop phức tạp, các UI toolkit mệnh lệnh kiểu cũ lại thường rất thực dụng, có nhiều widget đã được kiểm chứng và các pattern cũng quen thuộc. Ngược lại, các cách làm mới hơn lại buộc bạn phải tự tay xử lý mọi thứ từ những phần nhỏ, và chỉ cần phức tạp lên một chút là đã rất mệt
    • Tôi tò mò thường gặp những ý kiến phản đối nào với câu “không dùng GTK cũng được, nhưng có người lại nghĩ như thể đó không phải quyền lựa chọn của người khác”. Theo tôi, GTK làm khá tốt những phần như accessibility hay nhập liệu không dùng bảng chữ cái Latin, vốn là những thứ mà các lập trình viên tự làm thường không để ý, nên có vẻ đây mới là điểm cạnh tranh chính của nó
  • Một điều thú vị là trong Ghostty và một số ứng dụng GTK khác, khi chuột đi ra ngoài cửa sổ rồi quay lại, cú click cuộn đầu tiên sẽ bị bỏ qua. Nguyên nhân là một lỗi rất cũ, được báo từ tận năm 2015. Link lỗi. Đến giờ vẫn chưa có kế hoạch sửa, và maintainer thì có quan điểm là cứ chờ Wayland

    • Trên thực tế có vẻ vấn đề này không nằm ở chính GTK mà ở XInput2. Dĩ nhiên GTK có thể lách qua bằng các heuristic như Chromium đang dùng, nhưng về gốc rễ thì đây là vấn đề ở tầng trên, tức XInput2
    • Nếu đọc bug report đó và các issue được liên kết, có thể thấy đã có nhiều lần cố sửa, nhưng rốt cuộc đều buộc phải dựa vào vài heuristic và liên tục phát sinh tác dụng phụ còn nghiêm trọng hơn cả vấn đề ban đầu. Cuối cùng vì đây là vấn đề bắt nguồn từ nền X11, nên có lẽ phải sửa ở tận gốc thì các cải thiện khác mới thật sự có ý nghĩa. Nhưng hiện giờ X11 về cơ bản đang ở chế độ chỉ bảo trì, nên chừng nào vẫn còn người hâm mộ khăng khăng rằng “nó chạy hoàn hảo rồi, chẳng cần làm thêm gì nữa” thì khó mà kỳ vọng được. Rốt cuộc cách duy nhất còn lại là chờ chuyển sang Wayland
  • Ở đoạn “đã dùng Valgrind để kiểm chứng mọi bước”, thật ra đây là điều quá hiển nhiên nhưng tôi chưa từng thực sự làm, và cũng hiếm thấy lập trình viên nào khác làm vậy. Thường thì Valgrind chỉ được dùng khi đã xuất hiện bug cụ thể hoặc suy giảm hiệu năng. Nếu chủ động dùng Valgrind trong suốt quá trình phát triển, nhất là Memcheck và Helgrind, có lẽ độ ổn định của công cụ sẽ tăng lên rất nhiều, và bug cũng có thể bị bắt ngay lúc được đưa vào, thay vì sau đó phải lần mò qua hàng trăm commit

    • Cá nhân tôi khi dùng C và C++ luôn chạy valgrind định kỳ. Những lỗi mà valgrind và asan bắt được thường không dẫn đến crash ngay lập tức mà là các lỗi khó thấy, xuất hiện ngắt quãng nhưng cực kỳ rắc rối, nên rất khó truy nguyên nguyên nhân. Trong số đó cũng có cả lỗ hổng bảo mật. Ngoài ra, khi các rò rỉ bộ nhớ nhỏ tích lũy dần rồi về sau gây ra vấn đề lớn thật sự, thì việc đã có quá nhiều rò rỉ nhỏ từ trước lại càng làm khó xác định nguyên nhân. Vì thế dùng chủ động là rất đáng giá
    • Tôi từ trước đến nay vẫn chủ động dùng Valgrind, nhất là memcheck, để bắt trước những vấn đề dễ sửa trước khi đi sâu debug các bug report chi tiết. Tuy nhiên vấn đề lớn nhất là overhead hiệu năng quá cao, nên trải nghiệm chạy tương tác khá tệ. Nhưng chạy test qua Valgrind một lượt thì tôi nghĩ cực kỳ đáng
    • Tuy nhiên, Valgrind quá chậm và quá tốn kém để đưa thẳng vào vòng lặp sửa code-biên dịch-test. Nó có thể dùng trong chu kỳ test như nightly hay automation, nhưng để tích hợp cho tốt thì cần thêm công sức
  • Khi dùng Ghostty, việc không thể dán nhiều dòng vào nano trên Mac rất bất tiện. Có vẻ liên quan đến cách terminal xử lý “bracketed pasting”, nhưng lạ là iterm2 hay term thì không gặp vấn đề này

    • Nhìn chung tôi hài lòng 99% với Ghostty như một trình thay thế terminal, nhưng vấn đề copy-paste thì thực sự rất khó chịu và ngày nào cũng gặp
    • Từ khi dùng Ghostty làm terminal mặc định trên máy mới, điều tôi thấy tiếc nhất là không có chức năng tìm kiếm. Tôi thường xuyên dùng phím tắt để tìm nội dung cụ thể trong output, nhưng hiện chưa làm được. Thực tế đây cũng là vấn đề được nhắc nhiều nhất trong issue
    • Khi remote vào Ubuntu thì thậm chí không chạy được nano trong Ghostty
      $ nano
      Error opening terminal: xterm-ghostty.
      
      Cùng môi trường đó thì terminal của macOS hoặc terminal tích hợp của VS Code vẫn chạy bình thường
    • Trường hợp này thực sự có thể là bug, nên tôi khuyên nên báo bug
    • Thiếu tìm kiếm lệnh kiểu Cmd+F là điểm chí mạng nhất
  • Tôi tự hỏi nếu dùng Rust thay vì Zig thì liệu có tránh được lỗi bộ nhớ không. Vì phần lớn vấn đề đều đến từ tương tác Zig/C nên có lẽ Rust cũng sẽ tương tự. Tôi đoán từ góc nhìn của một lập trình viên Go, và cũng tò mò không biết khi phải tích hợp quy mô lớn với C thì có ngôn ngữ nào cung cấp nhiều công cụ an toàn hơn không

    • Nếu là Rust thì có thể ngăn được một vấn đề, nhưng phần còn lại vẫn vậy. Như bạn nói, tất cả đều nằm ở ranh giới C API và ngữ nghĩa của nó, nên mức độ an toàn thực tế phụ thuộc vào chất lượng của wrapper. Rust có hệ sinh thái wrapper đã được kiểm chứng khá tốt, nên ở điểm đó rủi ro có lẽ thấp hơn wrapper tự viết bằng Zig, nhưng nhìn chung cũng không khác biệt lớn. Ví dụ, lỗi truy cập bộ nhớ không xác định mà Rust có lẽ sẽ bắt được chính là phần đã được sửa trong PR này. Trên thực tế bộ nhớ sai chỉ bị sao chép vào frame đầu tiên, nhưng không bị dùng hay gửi đi đâu nên không nghiêm trọng. Dù vậy thì rõ ràng vẫn là không chính xác
    • Ngay cả Rust cũng đòi hỏi quản lý bộ nhớ và vòng đời thủ công ở ranh giới FFI với C/GObject. Borrow checker của Rust không thể kiểm chứng việc dùng bộ nhớ của mã bên ngoài
    • Một trong các ý chính của bài là với tổ hợp zig + valgrind, họ gặp ít vấn đề bộ nhớ hơn kỳ vọng rất nhiều
    • Viết C binding bằng Rust khó hơn nhiều. Vì vậy có khi chính việc tạo GTK binding bằng Rust cũng đã không khả thi
  • Khi dùng các ứng dụng dựa trên GPU như Ghostty, Alacritty, WezTerm, Zed, v.v., tôi thấy chúng nhanh hơn và dễ chịu hơn. Nhưng trớ trêu là cũng chính các ứng dụng này làm lộ ra giới hạn của driver Nvidia rõ hơn nhiều. Trước đây tôi gần như không dùng GPU nên không nhận ra, nhưng cả trong môi trường không có compositor như Regolith i3wm lẫn môi trường sway/wayland, các vấn đề như chia sẻ màn hình, khôi phục sau sleep, crash, v.v. với driver nvidia đều quá tệ. Tôi đã thử nhiều phiên bản khác nhau (550/560/575/580) mà vẫn như nhau. Chỉ gần đây tôi mới nhận ra hóa ra từ xưa đến giờ nó đã tệ như vậy

    • Tôi cũng có trải nghiệm tương tự trên Wayland. Trên X11, khi tắt hiệu ứng compositing thì cả 1050Ti lẫn card AMD cũ hơn của tôi (cần driver radeon) đều chạy ổn. Ngược lại trên Wayland lại có các vấn đề như giật, crash, màn hình lỗi, v.v.
  • Tôi đã từng làm được một ứng dụng lớn mà hệ thống kiểu của GTK hầu như không ảnh hưởng đến code. Nhưng bù lại, thay vì kế thừa hay mở rộng class, tôi nối mọi thành phần với nhau chỉ bằng cách bind lambda. Kết quả cuối cùng không quá bừa bộn, nhưng có lẽ những lập trình viên quen phong cách GTK chính thống sẽ thấy khá khó hiểu

  • Tôi không hiểu nổi sự quan tâm có phần quá mức dành cho Ghostty. Với một UI chỉ có tab và context menu, tôi nghi ngờ liệu có đáng để làm tất cả công việc tích hợp và viết lại như vậy không. Tôi đoán có lẽ họ định thêm cả một môi trường GUI mạnh như iterm2. Kitty thì tự vẽ tab bằng OpenGL nên có thể tùy biến hoàn toàn, đồng thời khỏi mất thời gian tích hợp với framework phức tạp để sớm triển khai các tính năng rất thực dụng như bọc kết quả lệnh cuối vào pager để hiển thị. Kitty cũng hỗ trợ remote tốt

    • UI của Ghostty không chỉ có tab, mà còn có split, banner “tiến trình đã kết thúc”, hộp thoại xác nhận đóng, hộp thoại đổi tiêu đề, phát hiện dán không an toàn, chuông thông báo có hoạt ảnh, terminal kéo xuống, thanh tiến trình, v.v. Trên Mac còn có tích hợp Apple Shortcuts và Spotlight. Dĩ nhiên ngay cả khi có những thứ này thì vẫn có thể tự triển khai mà không cần GUI toolkit, nhưng sứ mệnh của Ghostty là dùng toolkit native của từng nền tảng để ứng dụng tạo cảm giác “thật sự native”. Nếu không thích cách tiếp cận này thì dùng tab dạng text như Kitty cũng là một lựa chọn tốt. Tùy vào giá trị và ưu tiên theo đuổi mà chọn thôi. Sắp tới họ cũng chuẩn bị nhiều mở rộng GUI hơn và sẽ tích hợp sâu hơn với các tính năng native theo từng nền tảng như đồng bộ iCloud
    • Về ý “Kovid triển khai tính năng nhanh hơn”, nên cẩn thận vì tài khoản này có lịch sử khiến người ta nghi đó chính là Kovid. Tôi từng thấy trên HN và Reddit kiểu giới thiệu Kitty như thể trung lập nhưng lại công kích nhà phát triển khác. Có thể tham khảo cả lịch sử bình luận trước đây qua link này
    • Ngoài phần giải thích tích cực ở trên, tôi còn nghĩ sự xuất hiện của ‘libghostty’ là một yếu tố thay đổi cuộc chơi. Nó là một terminal implementation mạnh mẽ kiểu drop-in, ai cũng có thể dùng ngay tương tự WebKit
    • Tôi cũng đã lang thang thử đủ loại terminal, và dù Ghostty không hoàn toàn lý tưởng, cuối cùng tôi vẫn chuyển sang nó khi không tìm được thứ nào khác đủ ổn. Chỉ riêng điều đó với tôi cũng đã rất có ý nghĩa. Thường thì ưu điểm không nằm ở một “lý do quyết định” nào cụ thể, mà ở chỗ không có vấn đề lớn nào khiến tôi phải rời đi
    • Font được render trên Ghostty đẹp hơn hẳn so với Kitty. Neovide còn đẹp hơn nữa, nhưng hiện vẫn chưa hỗ trợ tab và cũng tốn pin hơn