Giữ Dock đứng yên một màn hình trên macOS đa màn hình

Tóm tắt

Trên opera-macbook chạy hai màn hình (built-in Liquid Retina XDR + Samsung LF24T450F), Dock nhảy sang bất kỳ màn hình nào con trỏ chạm vào cạnh dưới. Mẹo defaults write com.apple.dock autohide-delay — nền tảng của hai app Automator tự chế trước đây — đã hết tác dụng từ macOS Sequoia, và được xác minh là vô hiệu trên macOS 26.5.2 ngày 2026-08-21. Giải pháp thay thế duy nhất còn hiệu lực là chặn ở tầng sự kiện chuột bằng cgeventtap; đã tự viết lại app Dock Stick Forever v2 theo hướng này.

Điểm chính

  • macOS chuyển Dock sang màn hình mà con trỏ ép vào cạnh dưới. Đây là hành vi mặc định, không có tuỳ chọn tắt trong System Settings.
  • Mẹo cũ đặt autohide-delay thành một số rất lớn (999999 hoặc 31536000 giây), với ý tưởng kéo dài vô hạn khoảng chờ trước khi Dock chịu đổi màn hình.
  • Mẹo này chết từ macOS Sequoia. Apple đổi hành vi Dock đa màn hình và trả lời trên Developer Forums rằng đó là expected behavior, không phải lỗi.
  • Không có key defaults nào thay thế. Repo JakubBelau/DockAnchor quảng cáo “one-command fix” nhưng mở install.sh ra thì chính là mẹo autohide-delay cộng LaunchAgent giữ giá trị — tức đã hỏng sẵn.
  • Cách còn hiệu lực: dựng CGEventTap, chặn con trỏ không cho chạm dải vài điểm sát cạnh dưới của các màn hình không được neo. Điều kiện kích hoạt của Dock không bao giờ đúng vì con trỏ không bao giờ tới đó.

Bằng chứng

Xác minh mẹo cũ đã chết trên chính máy này (2026-08-21, macOS 26.5.2 build 25F84):

  • defaults read com.apple.dock autohide-delay3.1536e+07 (Dock Lock đã áp dụng).
  • ~/Library/Preferences/com.apple.dock.plist sửa lúc 09:22; tiến trình Dock khởi động lúc 09:22:50.
  • Vì Dock khởi động sau khi plist được ghi, giá trị chắc chắn đã được nạp. Loại trừ giả thuyết “quên killall Dock” và “giá trị bị macOS reset”.
  • Dock vẫn nhảy màn hình bình thường → mẹo được đọc nhưng không còn ảnh hưởng.

Hình học màn hình thật, đọc bằng CGDisplayBounds:

DisplayIDMainOriginSizemaxY
Built-in Liquid Retina XDR1(0, 0)1512×982982
Samsung LF24T450F3không(1512, −98)1920×1080982

Hai màn có cùng maxY — mép dưới thẳng hàng. Nên thuật toán phải phân biệt màn hình theo định danh, không thể dựa vào toạ độ đáy.

Lịch sử các bản tự chế

BảnVị tríKỹ thuậtTình trạng
Dock Stick Forever.app (2025-08-12)iCloud Automator DocumentsAutomator applet, một dòng defaults write autohide-delay 999999Vô hiệu từ Sequoia
Dock Lock.app v1.3 (2026-08-10)~/Applications/Automator applet, thêm dialog Áp dụng/Khôi phục, DELAY=31536000Vô hiệu từ Sequoia
Dock Stick Forever v2 (2026-08-21)~/Code/dock-stick-foreverSwift, CGEventTap chặn con trỏĐang dùng

Giải pháp đang dùng

App Swift tại ~/Code/dock-stick-forever, cài vào ~/Applications/Dock Stick Forever.app. Từ 2026-08-21 đã mở mã nguồn công khai (MIT) tại binhsonnguyen/dock-stick-forever, release v1.0.0 kèm bản build universal (arm64 + x86_64), ký ad-hoc không notarize:

  • Menu bar agent (LSUIElement), ~172 KB, ký ad-hoc.
  • Dựng CGEventTap.cgSessionEventTap / .headInsertEventTap, bắt mouseMoved và các sự kiện drag.
  • Với mỗi mẫu vị trí: nếu con trỏ nằm trong dải 4 pt sát cạnh dưới của màn hình không được neo thì ghi đè event.location, kéo lên mép dải. Chuyển động ngang không bị ảnh hưởng.
  • Định danh màn hình theo UUID (CGDisplayCreateUUIDFromDisplayID) chứ không theo CGDirectDisplayID, vì ID bị cấp lại sau khi ngủ/rút cáp/khởi động lại.
  • Rút màn hình đang neo thì tự lùi về màn hình chính — nếu không, mọi màn đều bị canh và con trỏ bị nhốt khỏi cạnh dưới ở khắp nơi.
  • Phần hình học tách thành module thuần (DockStickCore), có 9 test chạy bằng swift test không cần cắm màn hình hay rê chuột.

Đánh đổi: cần quyền Accessibility, có tiến trình chạy nền, và vài điểm sát đáy màn phụ không bấm được.

Các hướng khác đã cân nhắc

  • Tắt “Displays have separate Spaces” (System Settings → Desktop & Dock → Mission Control). Là cách chính thức duy nhất của Apple. Đổi lại: mất menu bar riêng mỗi màn, mất fullscreen độc lập từng màn, phải đăng xuất/đăng nhập lại. Máy đang bật tuỳ chọn này (key com.apple.spaces spans-displays chưa set = mặc định bật).
  • Đưa Dock sang trái/phải: Dock bám mép ngoài cùng của bố cục, hết đường nhảy. Rẻ nhất nhưng đổi thói quen dùng.
  • bwya77/DockAnchor (MIT, Swift): cùng kỹ thuật event tap, nhiều tính năng hơn (profile, sơ đồ màn hình trực quan). Lựa chọn tốt nếu không muốn tự bảo trì.

Khái niệm liên quan

Xác nhận hoạt động (2026-08-21)

Đã kiểm chứng thực tế trên máy, ở cấu hình mặc định: dải chặn 4 pt, không cần bật “chế độ mạnh”. Dock đứng yên ở màn built-in khi rê con trỏ xuống cạnh dưới màn Samsung.

Kết luận rút ra:

  • Ghi đè event.location trong event tap là đủ để window server dời con trỏ; không cần tới CGWarpMouseCursorPosition. Đường warp vẫn giữ trong app làm phương án dự phòng.
  • Dải 4 pt đủ vượt độ sâu kích hoạt thật của Dock, và mỏng tới mức không cảm nhận được khi thao tác.

Cùng ngày đã dọn dấu vết của cách tiếp cận cũ:

  • Xoá autohide-delayautohide-time-modifier khỏi com.apple.dock (autohide giữ nguyên 0). Đây từng là bẫy: để lại thì sau này bật auto-hide Dock sẽ không bao giờ hiện ra.
  • Chuyển Dock Lock.app và bản Automator Dock Stick Forever.app (trong iCloud Drive) vào Thùng rác.

Câu hỏi mở

  • Quyền Accessibility gắn với cdhash của bản ký ad-hoc, nên mỗi lần chạy lại build-app.sh là phải cấp quyền lại. Chưa quyết có đáng đăng ký Developer ID để ký ổn định không.