Presenting from a Mac without constantly turning around
Presenting from a laptop often means craning your neck to check what the audience sees. This guide configures an extended desktop so selected apps launch maximized on the projector, then mirrors that output back to a MacBook with click-through support; notes and tools stay on your screen and presentation content stays on theirs.
1. Set the projector as an extended display
System Settings → Displays → select the projector/external display → Use as: Extended display
2. Install and configure Hammerspoon
- Install Hammerspoon
- Grant Accessibility permission when prompted
- Hammerspoon menu bar icon → Open Config
- Paste the script below into
init.luaand save - Hammerspoon menu bar icon → Reload Config
-- Auto-move and maximize QuickTime Player and Preview windows
-- on the first external display.
hs.window.animationDuration = 0
local wf = hs.window.filter.new({
"QuickTime Player",
"Preview",
})
local function externalScreen()
local primary = hs.screen.primaryScreen()
for _, screen in ipairs(hs.screen.allScreens()) do
if screen:id() ~= primary:id() then
return screen
end
end
-- Fallback if no external display is connected.
return primary
end
local function moveAndMaximizeWindow(win)
if not win then return end
local winID = win:id()
if not winID then return end
local function apply()
local currentWin = hs.window.get(winID)
if not currentWin or not currentWin:isStandard() then
return
end
local targetScreen = externalScreen()
if targetScreen and currentWin:screen():id() ~= targetScreen:id() then
currentWin:moveToScreen(targetScreen, false, true, 0)
end
currentWin:maximize()
end
-- First pass after the window is created.
hs.timer.doAfter(0.25, apply)
-- Second pass catches apps that restore/resize shortly after opening.
hs.timer.doAfter(0.75, apply)
end
-- Only handle newly-created windows.
-- This avoids fighting manual resizing during the presentation.
wf:subscribe(hs.window.filter.windowCreated, moveAndMaximizeWindow)
-- Handle existing matching windows when Hammerspoon loads/reloads.
hs.timer.doAfter(0.5, function()
for _, win in ipairs(wf:getWindows()) do
moveAndMaximizeWindow(win)
end
end)
hs.alert.show("Auto-move/maximize QuickTime/Preview loaded")
Add or remove managed apps using their exact names in the Hammerspoon window filter.
3. Install and start Side Mirror
Install Side Mirror, select the projector/external display from drop down menu at top right, then click Start.
4. Presenting
Opening QuickTime Player or Preview-associated files will display them in full screen on the projector. Use Side Mirror on your MacBook to see and control the projected content. Clicking through moves focus and your cursor to the external display; recover via Opt+Shift+Return.
If you have multiple external displays, replace:
local function externalScreen()
local primary = hs.screen.primaryScreen()
for _, screen in ipairs(hs.screen.allScreens()) do
if screen:id() ~= primary:id() then
return screen
end
end
return primary
end
with:
local function externalScreen()
return hs.screen.find("BenQ") or hs.screen.primaryScreen()
end
Replace "BenQ" with a substring matching your projector's display name. You can list connected display names in the Hammerspoon console with:
hs.fnutils.each(hs.screen.allScreens(), function(s) print(s:name()) end)
❧ 2026-04-27