---
title: "同一個 Wi-Fi，手機就能看你的 Mac 螢幕：ScreenTask Mac 開源，以及一次把 macOS 弄到核心崩潰的教訓"
description: "Windows 上有個從 2014 年做到現在的開源小工具 ScreenTask，把螢幕變成區域網路裡的一個網頁，觀看者只需要瀏覽器。我沒找到合用的原生 Mac 版本，所以用 AI coding agent 做了一個並開源。這篇講它能做什麼、怎麼用，以及開發過程中一次真實的核心崩潰、兩輪對抗性覆核抓出的 12 個缺陷，和「修法本身也要再被審一次」這件事。"
author: "莊哲昀（AJ）"
tag: "Vibe Coding 實錄"
date: 2026-09-09
canonical: https://www.ahadigit.work/blog/screentask-mac
source: "blocks"
---

# 同一個 Wi-Fi，手機就能看你的 Mac 螢幕：ScreenTask Mac 開源，以及一次把 macOS 弄到核心崩潰的教訓

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

![ScreenTask Mac 應用程式圖示：深藍底、青綠色雙螢幕與無線傳送符號](https://www.ahadigit.work/assets/blog/screentask-mac-icon.jpg)

*ScreenTask Mac 的圖示。專案已開源在 GitHub，採 GPL-3.0-or-later。*

## 它做什麼：把螢幕變成區域網路裡的一個網址

ScreenTask Mac 在你的 Mac 上開一個小小的 HTTP 服務，持續把目前螢幕的畫面編成 JPEG 送出去。同一個 Wi-Fi 或有線網路裡的任何裝置，打開瀏覽器輸入一串網址，例如 `http://192.168.1.10:7070/`，就能看到你的畫面。手機、平板、別台筆電都能當觀看端，因為只需要瀏覽器。（誠實聲明：目前的自動化驗證都在同一台 Mac 上完成，跨裝置實測的狀況見文末「誠實的限制」。）

使用方式就三步：

1. 到 GitHub Releases 下載 ZIP，解壓縮後把 App **拖到「應用程式」**再開啟（為什麼一定要搬，後面會講，這不是慣例問題）。首次使用要在系統設定允許「螢幕錄製」，允許後要先結束 App 再重新開啟。
2. 選要分享的螢幕和網路介面，按「開始分享」。給別的裝置看，介面不要選 127.0.0.1。
3. 把畫面上顯示的網址告訴大家。分享者按「停止分享」，服務就關閉。

功能面它把 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。

崩潰報告指向的路徑，落在「每一條新連入的網路連線都會執行的系統網路政策檢查」。兩個條件疊在一起才會出事：

1. **App Translocation**：macOS 對從網路下載、帶有隔離屬性的 App，如果不是從「應用程式」資料夾執行，會改在一個唯讀的影子掛載點執行。這是 Gatekeeper 的安全機制，一般使用者完全感覺不到它存在。
2. **每張畫面一條連線**：第一版觀看頁用最直覺的做法，計時器預設每 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 第一條寫著「請不要直接從下載資料夾開啟」。

> **順帶一提：這也是為什麼「下載後直接雙擊」在 Mac 上不是好習慣**
>
> App Translocation 對大多數 App 來說感覺不出差別，但它確實讓程式跑在一個和正常安裝不同的唯讀環境，遇到邊緣狀況時行為可能不一樣。從 ZIP 解壓出來的 App，養成先拖進「應用程式」再開的習慣。

## 兩輪對抗性覆核，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。

## 資料來源

- [ScreenTask Mac 原始碼與下載（GitHub，AndyJuang/ScreenTaskMac）](https://github.com/AndyJuang/ScreenTaskMac)
- [ScreenTask Mac 0.1.3 發行說明](https://github.com/AndyJuang/ScreenTaskMac/releases/tag/v0.1.3)
- [ScreenTask Windows 原始專案（EslaMx7/ScreenTask，依其 README 聲明為 GPL-3.0-or-later）](https://github.com/EslaMx7/ScreenTask)
