同一個 Wi-Fi,手機就能看你的 Mac 螢幕:ScreenTask Mac 開源,以及一次把 macOS 弄到核心崩潰的教訓
上課或開會時常碰到一種尷尬:投影幕就一面,後排看不清楚;或者根本沒有投影機,只有一群人各自帶著手機和筆電。這時候你想要的很單純:把我的 Mac 螢幕,丟到在場每個人自己的裝置上,不用裝 App、不用開帳號、不用連外網。Windows 上有個叫 ScreenTask 的開源小工具,從 2014 年就在做這件事;我沒找到合用的原生 Mac 版本,所以做了一個。

它做什麼:把螢幕變成區域網路裡的一個網址
ScreenTask Mac 在你的 Mac 上開一個小小的 HTTP 服務,持續把目前螢幕的畫面編成 JPEG 送出去。同一個 Wi-Fi 或有線網路裡的任何裝置,打開瀏覽器輸入一串網址,例如 http://192.168.1.10:7070/,就能看到你的畫面。手機、平板、別台筆電都能當觀看端,因為只需要瀏覽器。(誠實聲明:目前的自動化驗證都在同一台 Mac 上完成,跨裝置實測的狀況見文末「誠實的限制」。)
使用方式就三步:
- 到 GitHub Releases 下載 ZIP,解壓縮後把 App 拖到「應用程式」再開啟(為什麼一定要搬,後面會講,這不是慣例問題)。首次使用要在系統設定允許「螢幕錄製」,允許後要先結束 App 再重新開啟。
- 選要分享的螢幕和網路介面,按「開始分享」。給別的裝置看,介面不要選 127.0.0.1。
- 把畫面上顯示的網址告訴大家。分享者按「停止分享」,服務就關閉。
功能面它把 Windows 原版能做的事都補齊了,另外針對 Mac 做了一些調整:
- 多螢幕選擇:一次分享一個完整螢幕,可以選要不要帶滑鼠游標。
- 畫質與更新間隔可調:JPEG 品質、擷取間隔、最高寬度都能設,網路差就把品質調低。這些設定要先停止分享才能改,重新開始後生效。
- 私人模式:可以設帳號密碼,觀看頁和所有圖片端點都要通過驗證。注意這是 HTTP Basic,帳密和畫面都是明文,不是 TLS。密碼只有在你勾選之後才存進 macOS 鑰匙圈,否則不落地。
- 預設只接受同一子網路的來源:其他網段的連線直接拒絕。這是預設值,要放寬得自己打開。
- 選單列常駐、自動開始、自動最小化:上課前打開就好,不用每次點。
- 觀看端可以暫停、全螢幕、符合視窗或原始大小,分享端停止時觀看頁會顯示提示並自動重連。
它不做的事也要講清楚:沒有聲音、不能遠端操控、不穿透網際網路。它就是一面可以被很多人同時看的鏡子,不是遠端桌面,也不是視訊會議。
為什麼不直接用 AirPlay 或開個 Meet
因為那些方案都預設了某個條件。AirPlay 預設對方也是 Apple 裝置。Zoom 或 Google Meet 預設在場每個人都有網際網路、都有帳號、都願意裝 App 或登入。企業內訓的教室常常三者都不成立:訪客 Wi-Fi 不通外網、學員的公司筆電裝不了東西、講師的 Mac 又沒有 HDMI 轉接頭。
ScreenTask 這類工具的價值就在於它假設得最少:一個區域網路、一個瀏覽器。它不需要網際網路,分享時完全不連外;沒有帳號、沒有雲端、沒有人數授權上限。畫面留在記憶體,不寫圖片檔、不上傳雲端。
假設得最少的工具,能用的場合最多。
怎麼做出來的:原生、零依賴、AI agent 寫的
整個專案由 AI coding agent 在我的指揮下完成,我負責決定要做什麼、不做什麼,授權螢幕錄製,以及在自己的 Mac 上授權螢幕錄製、用本機瀏覽器把它跑起來看。技術選型一開始就定死三件事:
- 原生 Apple Silicon:SwiftUI 做介面、ScreenCaptureKit 擷取畫面、Network.framework 開 HTTP 服務。不是 Electron 包網頁,也不是跨平台框架。
- 零第三方執行依賴:沒有任何外部套件,觀看頁的樣式和程式全部內嵌,不載 CDN。建置只需要 Apple Command Line Tools,不需要完整 Xcode。
- 重寫,不移植程式碼:以 Windows 版的功能為參考,Swift 與網頁部分全部重新撰寫,沒有使用上游的 Logo、Bootstrap 或 C# 程式。授權沿用上游的 GPL-3.0-or-later,並保留對原作者的致謝。
第一個能用的版本很快就出來了:瀏覽器觀看、帳密、多螢幕、畫質設定、選單列、鑰匙圈,該有的都有,五組核心測試和真實的 loopback HTTP 整合測試都過,GitHub Actions 也綠了。如果故事在這裡結束,它就是一篇普通的「我用 AI 做了一個小工具」。
但接下來發生的事,才是我覺得值得寫下來的部分。
一次真實的核心崩潰
一份帶著隔離屬性、處在 App Translocation 狀態的建置版本,在分享畫面的過程中,整台 Mac 當掉重開機。不是 App 閃退,是 macOS 本身的核心崩潰(kernel panic)。系統版本是 macOS 26.6.2。
崩潰報告指向的路徑,落在「每一條新連入的網路連線都會執行的系統網路政策檢查」。兩個條件疊在一起才會出事:
- App Translocation:macOS 對從網路下載、帶有隔離屬性的 App,如果不是從「應用程式」資料夾執行,會改在一個唯讀的影子掛載點執行。這是 Gatekeeper 的安全機制,一般使用者完全感覺不到它存在。
- 每張畫面一條連線:第一版觀看頁用最直覺的做法,計時器預設每 150 毫秒抓一張新圖片,每一張都是一條新的 TCP 連線。實測 20 秒的觀看,會產生大約 140 條用完即關的連線。
換句話說,一個在隔離狀態下執行的 App,用每秒好幾條新連線的頻率,持續敲那條有問題的核心路徑。這個崩潰我沒有辦法穩定重現,所以誠實地說,後面做的是規避,不是證明修好了。但規避的方向很明確:把兩個條件都拿掉。
改法一:一位觀看者兩條長連線
0.1.3 版把觀看頁改成 multipart/x-mixed-replace 串流:一條連線開著,分享端有新畫面就推過去,瀏覽器原生支援,不需要任何 JavaScript 函式庫。另外加一條每 3 秒一次、幾乎沒有內容的 HEAD 心跳,用來察覺分享端已經停止,因為串流模式下瀏覽器不會觸發錯誤事件,舊版的畫面會停在最後一張而不自知。不支援串流的瀏覽器則自動降級成逐張更新,但改用 keep-alive 重用同一條連線。
同樣 20 秒的觀看,連線數從約 140 條變成 0 到 1 條。
改法二:主動偵測隔離狀態
App 啟動時會檢查自己是不是在 App Translocation 狀態下執行。是的話,跳出提示問你要不要複製到「應用程式」並重新開啟,原本那份會保留。自動開始分享的功能會等這個提示處理完才綁定連接埠,避免在隔離狀態下就開始接連線。要老實說的是:這條搬移流程已經實作並有測試,但還沒有從一份真正下載回來的隔離副本上互動實跑過,所以請優先自己手動拖到「應用程式」。這就是為什麼 README 第一條寫著「請不要直接從下載資料夾開啟」。
兩輪對抗性覆核,12 個缺陷,和一個「修了反而更糟」的案例
改完串流之後,我沒有直接發版,而是讓另一個完全沒有前文脈絡的 agent 用攻擊者的角度審這份改動。這是我現在做任何東西的固定流程:寫的人不能自己驗收,不管寫的人是人還是模型。
第一輪就抓到一個很嚴重的洞:串流連線完全沒有逾時。這很合理,串流本來就該一直開著。但後果是,區域網路裡任何一台裝置只要開 256 條串流連線然後不讀取,就能占滿整張連線表,其他人全部連不上。一台手機就能讓整間教室看不到畫面。
於是加了修法:寫入超過 10 秒沒有進度就放掉那條連線的名額。看起來合理,測試也過了。
第二輪覆核發現,這個修法只放掉了帳面上的名額,通訊端本身還活著。結果是攻擊者可以一直換新連線,每一條都被計為「已釋放」,但底層的 socket 和核心緩衝區一直堆積。實測同一來源連續四波各 8 條不讀取的連線,通訊端數量成長到 32 條、記憶體 4.70 MB,而且不會停。第一版修法把一個「有上限的拒絕服務」變成了「沒有上限的資源洩漏」,比原本的問題還糟。
正確的做法是:逾時就直接重置那條連線,名額要等系統真的收掉通訊端之後才釋出。修好後同樣的攻擊停在 16 條不再成長,最後一波結束後大約 20 秒內全部歸零,其他裝置全程可以正常觀看。但這不等於惡意裝置毫無影響:單一位址仍然可以占住 24 條閒置名額,那是刻意留給同一位址多台裝置的餘裕。以上數字也都是 loopback 加合成畫面量出來的,真實全螢幕 JPEG 還沒量過。
修法本身也是一份改動,也要再被審一次。第一輪抓到的問題,第二輪要回頭看修法有沒有引進新的問題。
兩輪下來總共確認 12 個缺陷,全部修掉並補上測試。其他幾個比較有代表性的:
- Safari 按暫停或切到背景分頁後,其實還在繼續收畫面、連線也還留著。現在會確實中斷。
- 斷線期間的重連嘗試會被誤判成「這個瀏覽器不支援串流」,整個工作階段永久降級成逐張更新,回不去了。另外,原本要在 30 秒後把觀看者升回串流的計時器,只要暫停或切換分頁一次就被清掉、不再重排,手機鎖一次螢幕就永遠停在逐張模式。
- 帶有 body 的請求會讓 keep-alive 連線錯位,因為伺服器從不讀 body,那段內容會被當成下一個請求解析;現在這類請求直接回應後關閉連線。順帶一提,每來源 24 條上限、每位觀看者占兩條,所以同一個 IP 大約 12 位觀看者,整體 256 條大約 128 位。這是資源保護,不是「無限人數」的承諾。
測試也跟著長出來。除了核心回歸測試和 loopback 整合測試,新增了一支腳本用 Playwright 分別以 Chromium、WebKit、Firefox 三種引擎真的打開觀看頁,檢查串流播放、暫停釋放連線、分享端停止與復原、降級與回升、以及連線數。CI 上每次都跑 Chromium 和 WebKit。這些是開發工具,不隨 App 發布。
誠實的限制
- 發行包是 ad-hoc 簽署、未經 Apple 公證。我目前沒有申請 Apple Developer ID。所以第一次打開時 Gatekeeper 可能會擋,你要到「系統設定 → 隱私權與安全性」選「仍要打開」。不需要關閉整台電腦的安全機制,也可以直接從原始碼自己建。建置腳本備有正式簽署與公證的路徑,但我沒有憑證,那條路徑還沒實際跑過。
- HTTP 未加密。畫面和 Basic 帳密都是明文,私人模式不是 TLS。只適合可信任的區域網路;要跨網路請走受信任的 VPN,不要把它直接暴露在網際網路上。
- 只支援 M 系列處理器、macOS 13 以上。建置腳本固定產生 arm64,Intel Mac 不在支援範圍。
- 硬體覆蓋不完整。所有驗證都在同一台 Mac 的 loopback 上完成,還沒有用第二台實體裝置或真實 Wi-Fi 測過;手機、平板(包括 iOS Safari)當觀看端目前仍屬未驗證,多個實體螢幕也是。哪些測了、哪些沒測,repo 裡的驗證紀錄有逐項列出。
- 停止分享無法回收已送出的畫面。觀看者的瀏覽器可能保留看過的畫面,也可以自己截圖。分享之前先想清楚要給誰看。
這個專案教我的事
AI coding agent 把「做出一個能動的東西」的成本壓到很低,低到 0.1.2 和 0.1.3 兩個版本都在同一天內做完。但這個專案裡真正花時間的部分,全部發生在「能動」之後:一次無法重現的核心崩潰、兩輪覆核、12 個缺陷、一個修了反而更糟的修法。
如果只看「功能做完、測試全綠、CI 通過」就發版,0.1.2 就會是最終版本,然後第一個從下載資料夾直接打開它的使用者,會經歷一次莫名其妙的重開機。能動和能交付之間的距離,才是現在做軟體真正的工作量。agent 沒有讓這段距離消失,它只是讓你更早、更便宜地走到起點。
程式碼、發行版、功能對照表、驗證紀錄都在 GitHub。如果你在教室或會議室裡用得上,或者發現它在你的環境裡有問題,歡迎開 issue。
想拿這篇的純文字原稿?網址結尾加上 .md 即可: /blog/screentask-mac.md