host/macos: Frametop Streams, a menu bar icon that turns the displays on and off

Frametop Streams lists the Mac's displays (its Sunshine instances) and turns
each one, or all of them, on or off. ft-sunshine off|on|status does the same
in Terminal. Off disables the instance's LaunchAgent, so it stays off through
logins until on. The setup compiles the app from Frametop Streams.applescript
(AppleScriptObjC, no compiler) and starts it at login; --undo removes it.

README.md is the Mac setup guide, also the notes of the kit's release.
This commit is contained in:
DeeJanuz committed 2026-10-12 09:38:24 -06:00
1 parent 9290ae6c0e
commit 188f107cfc
6 files changed
+430 -4

No files matched your search

+1 -1
View File
@@ -194,7 +194,7 @@ remote-displays/install.sh # its menu entry
On the PC, `host/windows/Setup Frametop host.cmd` sets it up for Frametop: Vibepollo 2.0.0 (installed if missing), Frametop's build of its `sunshine.exe`, the settings Frametop needs, the Web UI login you sign in with from the Frame, and a firewall check (`-Check` to see what it would change, `-Undo` to put things back).
On a Mac, `host/macos/frametop-host-setup.sh` (or `Setup Frametop host.command`) installs Sunshine and runs one instance per real display, each on its own port (47989 + 100·N), started at login. You allow Sunshine Screen Recording and Accessibility once in System Settings. Without Accessibility the Frame's pointer moves on the Mac but clicks, scrolling and typing don't arrive; the setup checks both, and offers to ask again if Accessibility is missing. Remote Displays finds the Mac as one computer; signing in with the Web UI login pairs the displays you pick, and the password isn't kept, so adding more later signs in again. Macs have no virtual displays (`--check`, `--undo` as on the PC).
On a Mac, `host/macos/frametop-host-setup.sh` (or `Setup Frametop host.command`) installs Sunshine and runs one instance per real display, each on its own port (47989 + 100·N), started at login. You allow Sunshine Screen Recording and Accessibility once in System Settings. Without Accessibility the Frame's pointer moves on the Mac but clicks, scrolling and typing don't arrive; the setup checks both, and offers to ask again if Accessibility is missing. Remote Displays finds the Mac as one computer; signing in with the Web UI login pairs the displays you pick, and the password isn't kept, so adding more later signs in again. Macs have no virtual displays (`--check`, `--undo` as on the PC). Frametop Streams in the menu bar turns each display on or off with a click, or all of them, and `ft-sunshine off|on|status` does the same in Terminal. Off lasts through logins until on. [host/macos/README.md](../host/macos/README.md) has the steps for setting up a Mac.
## Floating windows
+1
View File
@@ -462,6 +462,7 @@ The user decided remote displays are Frametop displays, with the same controls a
**The Mac: one Sunshine instance per display (2026-10-10, branch `mac-host`):**
- `host/macos/frametop-host-setup.sh` (or `Setup Frametop host.command`): installs the pinned Sunshine pre-release (SHA-256 checked, notarized by LizardByte), lists the displays (`displays.js`, JavaScript for Automation, no compiler), and writes an instance per display in `~/Library/Application Support/Frametop/sunshine/instance-N`, the main display's first, each started at login by the LaunchAgent `frametop.sunshine.N`. Its settings: `port`, `sunshine_name` "COMPUTER: DISPLAY (WxH)", its own state and apps files, `minimum_fps_target = 1`, and the menu bar icon on the first only. The rest of each `sunshine.conf` (Web UI changes) is kept on a rerun. `--check`, `--undo` (removes the LaunchAgents; settings, pairings and Sunshine stay), `--set-login`, `--skip-login`.
- Turning them off and on (2026-10-12): Frametop Streams, a menu bar icon, lists the displays with a check by each one that's on. A click turns one on or off, Turn All On and Turn All Off do every one, and the icon dims while they're all off. It's `Frametop Streams.applescript`, a stay-open AppleScriptObjC app (`NSStatusItem`, so no compiler) that the setup compiles into `~/Applications`, marks `LSUIElement` (no Dock icon), signs again ad hoc after that change, and starts at login with the LaunchAgent `frametop.streams`. Quit from its menu, it stays quit until the next login. It runs `ft-sunshine off|on|status [--tsv] [N...]`, in the background for a change, with the output in `~/Library/Logs/Frametop/streams.log`. `ft-sunshine` is installed next to `ft-sunshine-instance` and linked into `~/.local/bin` when there is one. Off disables each instance's LaunchAgent (`launchctl disable`) and stops it, so it stays off through logins. On enables it and starts it, or starts one quit from Sunshine's own menu bar icon, which launchd leaves stopped after a clean exit. Rerunning the setup turns them all on. The Frame's streams of the Mac keep trying while it's off and reconnect after on. Tested with throwaway LaunchAgents serving fake instances, through the menu (UI scripting) and `ft-sunshine`: on, on again (the running one kept its pid), off, on after off, one display, a stopped one, unknown numbers, a display that isn't connected, and the app started by its LaunchAgent and quit from its menu. On the M3 Max: the setup from the kit zip, unpacked with a download's quarantine (the installed scripts keep it, and run anyway: launchd and the app run them through bash and sh), which turned the three on; then Turn All Off, Turn All On and Turn All Off again from the menu, each done within 3 s.
- An instance is tied to its monitor by vendor, model and serial, not to a display id: `ft-sunshine-instance` looks the id up at every start and passes `output_name=ID`. While the monitor is unplugged it doesn't start Sunshine, which would fall back to the main display, and launchd tries again.
- One certificate for all the instances, so Remote Displays pins one key per Mac; one Web UI login file for all, kept from an earlier Sunshine install if there was one. An earlier Homebrew Sunshine's LaunchAgent (the same ports) is turned off, and back on with `--undo`.
- Screen Recording and Accessibility are granted by hand, once for all the instances: macOS lists them as Sunshine (the LaunchAgent runs a bash script that execs it; `AssociatedBundleIdentifiers` makes Login Items say Sunshine too). Sunshine started from a terminal gets that terminal's grants instead.
+139
View File
@@ -0,0 +1,139 @@
-- Frametop Streams: a menu bar icon for this Mac's Frametop displays (its Sunshine instances, one
-- per display). Click a display to turn it on or off, or turn them all on or off. It runs
-- ft-sunshine. frametop-host-setup.sh compiles it into ~/Applications as a stay-open app with no
-- Dock icon and starts it at login (the LaunchAgent frametop.streams).
use AppleScript version "2.4"
use framework "Foundation"
use framework "AppKit"
use scripting additions
property statusItem : missing value
property theMenu : missing value
property theTimer : missing value
property tool : ""
property logFile : ""
property busyPid : 0 -- the ft-sunshine started from the menu, while it runs
property busyText : ""
property ticks : 0
on run
set tool to quoted form of ((POSIX path of (path to application support folder from user domain)) & "Frametop/sunshine/bin/ft-sunshine")
set logFile to (POSIX path of (path to library folder from user domain)) & "Logs/Frametop/streams.log"
set statusItem to current application's NSStatusBar's systemStatusBar()'s statusItemWithLength:(current application's NSVariableStatusItemLength)
set icon to current application's NSImage's imageWithSystemSymbolName:"display.2" accessibilityDescription:"Frametop Streams"
icon's setTemplate:true
statusItem's button()'s setImage:icon
statusItem's button()'s setToolTip:"Frametop Streams"
set theMenu to current application's NSMenu's alloc()'s initWithTitle:"Frametop Streams"
theMenu's setAutoenablesItems:false
theMenu's setDelegate:me
statusItem's setMenu:theMenu
refresh()
set theTimer to current application's NSTimer's scheduledTimerWithTimeInterval:2 target:me selector:"tick:" userInfo:(missing value) repeats:true
end run
-- Every 2 s while a change runs, otherwise every 10 s: the icon dims while every display is
-- off, whatever turned them off.
on tick:aTimer
set ticks to ticks + 1
if busyPid is not 0 or ticks mod 5 = 0 then refresh()
end tick:
on menuNeedsUpdate:aMenu
refresh()
end menuNeedsUpdate:
on refresh()
if busyPid is not 0 then
try
do shell script "kill -0 " & busyPid
on error
set busyPid to 0
end try
end if
theMenu's removeAllItems()
try
set rows to paragraphs of (do shell script tool & " status --tsv")
on error msg
addItem(msg, missing value, missing value, false)
addItem("Run frametop-host-setup.sh to set this Mac up.", missing value, missing value, false)
theMenu's addItem:(current application's NSMenuItem's separatorItem())
addItem("Quit Frametop Streams", "quitApp:", missing value, true)
statusItem's button()'s setAppearsDisabled:true
return
end try
set ready to busyPid is 0
set anyOn to false
set saved to AppleScript's text item delimiters
set AppleScript's text item delimiters to tab
repeat with r in rows
set f to text items of r
set n to (item 1 of f) as integer
set instState to item 2 of f
set label to item 3 of f
if instState is "waiting" then
set label to label & " (waiting for it to be connected)"
else if instState is "starting" then
set label to label & " (starting)"
else if instState is "stopped" then
set label to label & " (stopped)"
end if
set mi to addItem(label, "toggle:", n, ready)
-- A check while it's on; a dash while it's on but not streaming yet.
if instState is "on" then
mi's setState:(current application's NSControlStateValueOn)
set anyOn to true
else if instState is "waiting" or instState is "starting" then
mi's setState:(current application's NSControlStateValueMixed)
set anyOn to true
end if
end repeat
set AppleScript's text item delimiters to saved
theMenu's addItem:(current application's NSMenuItem's separatorItem())
if not ready then addItem(busyText, missing value, missing value, false)
addItem("Turn All On", "allOn:", missing value, ready)
addItem("Turn All Off", "allOff:", missing value, ready)
theMenu's addItem:(current application's NSMenuItem's separatorItem())
addItem("Quit Frametop Streams", "quitApp:", missing value, true)
statusItem's button()'s setAppearsDisabled:(not anyOn)
end refresh
on addItem(itemTitle, itemAction, itemTag, itemEnabled)
set mi to current application's NSMenuItem's alloc()'s initWithTitle:itemTitle action:itemAction keyEquivalent:""
if itemAction is not missing value then mi's setTarget:me
if itemTag is not missing value then mi's setTag:itemTag
mi's setEnabled:itemEnabled
theMenu's addItem:mi
return mi
end addItem
-- A display that's off or stopped turns on; one that's on, waiting or starting turns off.
on toggle:sender
set n to (sender's tag()) as integer
if ((sender's state()) as integer) = 0 then
change("on " & n, "Turning a display on…")
else
change("off " & n, "Turning a display off…")
end if
end toggle:
on allOn:sender
change("on", "Turning every display on…")
end allOn:
on allOff:sender
change("off", "Turning every display off…")
end allOff:
-- In the background: on waits up to 20 s for the instances to answer. Its output goes to the log.
on change(args, what)
set busyText to what
set logDir to do shell script "dirname " & quoted form of logFile
set busyPid to (do shell script "mkdir -p " & quoted form of logDir & "; { date; " & tool & " " & args & "; } >> " & quoted form of logFile & " 2>&1 & echo $!") as integer
refresh()
end change
on quitApp:sender
theTimer's invalidate()
tell me to quit
end quitApp:
+64
View File
@@ -0,0 +1,64 @@
# Frametop host for macOS
This kit makes a Mac a host for Frametop's remote displays: the Mac's monitors appear as screens on a Steam Frame. Vibepollo has no macOS build, so the Mac uses LizardByte's Sunshine, one copy (an instance) per display. Frametop Streams, a menu bar icon, turns them on and off.
## Requirements
- A Mac with Apple silicon, on macOS 14.2 or later.
- An admin account, to install Sunshine in /Applications.
- The Mac and the Steam Frame on the same network.
- Frametop on the Steam Frame.
## Set up
1. Download `frametop-macos-host-1.zip` and open it. You get a folder named `frametop-macos-host`.
2. Open Terminal and run the setup:
```
bash ~/Downloads/frametop-macos-host/frametop-host-setup.sh
```
You can also double-click `Setup Frametop host.command`. The kit isn't signed, so macOS blocks it the first time. To allow it, open System Settings > Privacy & Security, click Open Anyway next to the message about it, then open it again.
3. The setup does the following:
- It installs Sunshine 2026.1010.141104 in /Applications after checking the download's SHA-256. This is a pre-release, and it's the first build where the mouse reaches displays other than the main one.
- It creates one Sunshine instance per display, on ports 47989, 48089, 48189 and so on, each started at login.
- It asks for the Web UI user name and password that the Steam Frame signs in with. The password is typed in Terminal and is never shown or saved. If an earlier run of this setup or an earlier Sunshine install already has a login, the setup uses that one instead.
- It adds Frametop Streams to the menu bar.
4. When macOS asks, allow Sunshine both Screen Recording and Accessibility in System Settings > Privacy & Security. Then go back to Terminal and press Enter. Without Accessibility, the pointer moves but clicks, scrolling and typing don't reach the Mac. The setup checks both permissions and offers to ask again if Accessibility is missing.
5. On the Steam Frame, open Frametop Remote Displays and choose Add computer. Pick this Mac, sign in with the Web UI login, and choose the displays to show.
Run the setup again after you connect a new monitor.
## Turn the streams on and off
Click the display icon in the menu bar to open Frametop Streams. It lists the Mac's displays and puts a check next to each one that's on. Click a display to turn it on or off, or choose Turn All On or Turn All Off. The icon is dimmed while every display is off. A display you turn off stays off after a restart, until you turn it on again.
You can do the same in Terminal with `ft-sunshine`:
```
ft-sunshine status # each display and whether it's on
ft-sunshine off # turn every display off
ft-sunshine on # turn every display on
ft-sunshine on 1 # turn on one display, by the number status shows
```
If you have a `~/.local/bin` folder, the setup puts `ft-sunshine` there. Otherwise, run it as `~/Library/Application\ Support/Frametop/sunshine/bin/ft-sunshine`.
While a display is off, the Steam Frame keeps trying to reach it, and it reconnects once the display is on again.
If you quit Frametop Streams, it starts again at your next login. You can also open it from Spotlight.
## Good to know
- The Steam Frame can't reach a Mac that's asleep. To keep the Mac awake on power, turn on System Settings > Battery > Options > Prevent automatic sleeping on power adapter when the display is off. The setup tells you if the Mac sleeps when idle.
- Sunshine's own menu bar icon belongs to the main display's instance. Quitting Sunshine from that icon stops only the main display. Frametop Streams or `ft-sunshine on` starts it again.
- Logs are in `~/Library/Logs/Frametop`.
## Check and remove
- `bash frametop-host-setup.sh --check` shows what the setup would change, without changing anything.
- `bash frametop-host-setup.sh --undo` removes the instances' LaunchAgents, Frametop Streams and `ft-sunshine`. Sunshine and its settings and pairings stay, and the setup says how to remove those too.
## Source
This kit is `host/macos` in [Frametop](https://github.com/Frametop/frametop), under the MIT license. Sunshine is [LizardByte's](https://github.com/LizardByte/Sunshine), under GPL-3.0. The setup downloads it from Sunshine's GitHub release.
+70 -3
View File
@@ -15,6 +15,9 @@
# LaunchAgent frametop.sunshine.N and started again if it stops. An instance is tied to its
# monitor, not to a port: plug the monitor in elsewhere and it keeps its instance, and while
# it's unplugged its instance waits for it. Run this again after connecting a new monitor.
# Frametop Streams, a menu bar icon started at login, turns each one on or off with a
# click, or all of them; ft-sunshine off|on|status does the same in Terminal. Off lasts
# through logins until on, or until this runs again.
# 3. The Web UI login you sign in with once from the Steam Frame, the same for every instance:
# keep the one there is, take the one an earlier Sunshine install had, or set one. The
# password is typed here and never shown or saved.
@@ -27,7 +30,8 @@
# --set-login set a new Web UI login even if there is one
# --skip-login leave the Web UI login alone
# --check only say what would change
# --undo remove the instances' LaunchAgents (settings, pairings and Sunshine stay)
# --undo remove the instances' LaunchAgents, Frametop Streams and ft-sunshine
# (settings, pairings and Sunshine stay)
# A log goes to ~/Library/Logs/Frametop/host-setup.log.
set -euo pipefail
@@ -45,6 +49,9 @@ BASE_PORT=47989
GUI="gui/$(id -u)"
OLD_STATE="$HOME/.config/sunshine/sunshine_state.json" # a stock Sunshine install's login
BREW_LABEL=homebrew.mxcl.sunshine
STREAMS_APP="$HOME/Applications/Frametop Streams.app"
STREAMS_LABEL=frametop.streams # its LaunchAgent and bundle id
LINK="$HOME/.local/bin/ft-sunshine" # only when there's a ~/.local/bin
HERE=$(cd "$(dirname "$0")" && pwd)
check=0 undo=0 login=auto
@@ -69,6 +76,8 @@ our_agents() { ls "$AGENTS" 2>/dev/null | sed -n "s/^\($LABEL\.[0-9][0-9]*\)\.pl
# bootout returns before the job is gone, and bootstrap fails until it is.
start_agent() {
local i
# ft-sunshine off disables the agents, and bootstrap fails while they are.
launchctl enable "$GUI/$1"
for i in 1 2 3 4 5; do
launchctl bootstrap "$GUI" "$AGENTS/$1.plist" 2>/dev/null && return 0
agent_loaded "$1" && return 0
@@ -84,6 +93,50 @@ stop_agents() {
done
}
# Frametop Streams: a menu bar icon that turns each instance on or off through ft-sunshine,
# started at login. It's a stay-open AppleScript app, so nothing needs compiling. LSUIElement
# keeps it out of the Dock, and changing Info.plist means signing it again (ad hoc, as osacompile
# does).
install_streams_app() {
local where="$DATA/bin/ft-sunshine" info="$STREAMS_APP/Contents/Info.plist"
if [ -d "$HOME/.local/bin" ] && { [ ! -e "$LINK" ] || [ -L "$LINK" ]; }; then
ln -sf "$DATA/bin/ft-sunshine" "$LINK"
where=ft-sunshine
fi
launchctl bootout "$GUI/$STREAMS_LABEL" 2>/dev/null || true
pkill -f "$STREAMS_APP/Contents/MacOS/applet" 2>/dev/null || true
mkdir -p "$(dirname "$STREAMS_APP")"
rm -rf "$STREAMS_APP"
if ! osacompile -s -o "$STREAMS_APP" "$HERE/Frametop Streams.applescript" 2>/dev/null ||
! plutil -replace LSUIElement -bool true "$info" ||
! plutil -replace CFBundleIdentifier -string "$STREAMS_LABEL" "$info" ||
! codesign -f -s - "$STREAMS_APP" 2>/dev/null; then
say "Couldn't make $STREAMS_APP. To turn the instances off or on: $where off|on."
return 0
fi
cat > "$AGENTS/$STREAMS_LABEL.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>$STREAMS_LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$STREAMS_APP/Contents/MacOS/applet</string>
</array>
<key>AssociatedBundleIdentifiers</key><string>$STREAMS_LABEL</string>
<key>RunAtLoad</key><true/>
<!-- Quit from its menu, it stays quit until the next login; after a crash it starts again. -->
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
<key>ProcessType</key><string>Interactive</string>
<key>LimitLoadToSessionType</key><string>Aqua</string>
</dict>
</plist>
EOF
start_agent "$STREAMS_LABEL" || true
say "Frametop Streams is in the menu bar: click a display there to turn it on or off. In Terminal: $where off|on|status."
}
# Answers when the instance on this port is up (its HTTP serverinfo, port N).
answers() { curl -s -m 2 -o /dev/null "http://127.0.0.1:$1/serverinfo"; }
wait_answers() {
@@ -204,6 +257,18 @@ undo() {
rm -f "$AGENTS/$label.plist"
say "Removed the LaunchAgent $label."
done
if [ -e "$AGENTS/$STREAMS_LABEL.plist" ] || [ -e "$STREAMS_APP" ] || [ -L "$LINK" ]; then
if [ $check = 1 ]; then
say "Would remove Frametop Streams and ft-sunshine."
else
launchctl bootout "$GUI/$STREAMS_LABEL" 2>/dev/null || true
pkill -f "$STREAMS_APP/Contents/MacOS/applet" 2>/dev/null || true
rm -f "$AGENTS/$STREAMS_LABEL.plist"
rm -rf "$STREAMS_APP"
[ "$(readlink "$LINK" 2>/dev/null)" = "$DATA/bin/ft-sunshine" ] && rm -f "$LINK"
say "Removed Frametop Streams and ft-sunshine."
fi
fi
if [ -e "$DATA/.disabled-$BREW_LABEL" ]; then
if [ $check = 1 ]; then
say "Would turn Homebrew's Sunshine LaunchAgent back on."
@@ -272,11 +337,13 @@ $n"
[ -n "$n" ] || continue
say "Would $([ -e "$DATA/instance-$n/display" ] && echo keep || echo add) instance $n: $name ($w x $h), port $((BASE_PORT + 100 * n))."
done <<< "$lines"
say "Would install Frametop Streams ($STREAMS_APP), a menu bar icon that turns each instance on or off, and ft-sunshine, the same in Terminal."
else
stop_agents
mkdir -p "$DATA/bin" "$DATA/credentials" "$AGENTS"
cp "$HERE/ft-sunshine-instance" "$HERE/displays.js" "$DATA/bin/"
chmod 755 "$DATA/bin/ft-sunshine-instance"
cp "$HERE/ft-sunshine-instance" "$HERE/ft-sunshine" "$HERE/displays.js" "$DATA/bin/"
chmod 755 "$DATA/bin/ft-sunshine-instance" "$DATA/bin/ft-sunshine"
install_streams_app
# One certificate for every instance, so Frametop pins one key for the Mac.
if [ ! -s "$DATA/credentials/cacert.pem" ] || [ ! -s "$DATA/credentials/cakey.pem" ]; then
(umask 077 && openssl req -x509 -newkey rsa:2048 -nodes -days 7300 -subj "/CN=Sunshine Gamestream Host" \
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
# Turns this Mac's Frametop displays (its Sunshine instances, one per display) on or off. The
# setup installs it, and the Frametop Streams menu bar app runs it.
# Usage: ft-sunshine on|off|status [--tsv] [N...]
#
# on start the instances, and start them at login again
# off stop them, and leave them stopped at login until "on"
# status each instance's display, port and state (the default). With --tsv, a line per
# instance: N, state (on, off, waiting, starting, stopped), display, port, tab-separated
#
# Without N it's every instance. Settings and pairings stay as they are. The Frame's streams of
# the Mac keep trying while it's off and come back by themselves after "on".
set -u
DATA="$HOME/Library/Application Support/Frametop/sunshine"
AGENTS="$HOME/Library/LaunchAgents"
LABEL=frametop.sunshine # instance N's agent is frametop.sunshine.N
BASE_PORT=47989
GUI="gui/$(id -u)"
usage() { sed -n '2,/^set -u/p' "$0" | sed '$d; s/^# \{0,1\}//'; }
cmd=${1:-status}
[ $# -gt 0 ] && shift
case $cmd in
on|off|status) ;;
-h|--help|help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
tsv=0
nums=()
for arg in "$@"; do
case $arg in
--tsv) tsv=1 ;;
*) nums+=("$arg") ;;
esac
done
set -- ${nums[@]+"${nums[@]}"}
loaded() { launchctl print "$GUI/$LABEL.$1" >/dev/null 2>&1; }
answers() { curl -s -m 2 -o /dev/null "http://127.0.0.1:$((BASE_PORT + 100 * $1))/serverinfo"; }
state() {
if ! loaded "$1"; then
echo off
elif answers "$1"; then
echo on
elif [ -e "$DATA/instance-$1/.waiting" ]; then
echo waiting
elif launchctl print "$GUI/$LABEL.$1" | grep -q 'state = running'; then
echo starting
else
# Quit from Sunshine's menu bar icon: launchd doesn't start it again after a clean exit.
echo stopped
fi
}
status() {
local n st name port
for n in "${instances[@]}"; do
st=$(state "$n")
name=$(cut -f2 "$DATA/instance-$n/display")
port=$((BASE_PORT + 100 * n))
if [ $tsv = 1 ]; then
printf '%s\t%s\t%s\t%s\n' "$n" "$st" "$name" "$port"
continue
fi
case $st in
waiting) st="on, waiting for the display to be connected" ;;
stopped) st="stopped (quit from Sunshine's menu bar icon)" ;;
esac
echo "Instance $n, $name (port $port): $st"
done
}
start() {
local i
# off disables the agent so it stays off at login; bootstrap fails while it's disabled.
launchctl enable "$GUI/$LABEL.$1"
if loaded "$1"; then
# Loaded but stopped (quit from the menu bar): start it. One that's running is left alone.
launchctl kickstart "$GUI/$LABEL.$1" >/dev/null 2>&1
return 0
fi
# bootout returns before the job is gone, and bootstrap fails until it is.
for i in 1 2 3 4 5; do
launchctl bootstrap "$GUI" "$AGENTS/$LABEL.$1.plist" 2>/dev/null && return 0
loaded "$1" && return 0
sleep 1
done
echo "Couldn't start instance $1's LaunchAgent $LABEL.$1." >&2
return 1
}
stop() {
launchctl disable "$GUI/$LABEL.$1"
launchctl bootout "$GUI/$LABEL.$1" 2>/dev/null || true
}
# Every instance the setup made, or the ones named.
instances=()
if [ $# -gt 0 ]; then
for n in "$@"; do
if ! [[ $n =~ ^[0-9]+$ ]] || [ ! -e "$DATA/instance-$n/display" ] || [ ! -e "$AGENTS/$LABEL.$n.plist" ]; then
echo "There's no instance $n; ft-sunshine status lists them." >&2
exit 2
fi
instances+=("$n")
done
else
for d in "$DATA"/instance-*/display; do
[ -e "$d" ] || continue
n=$(basename "$(dirname "$d")" | sed 's/^instance-//')
[ -e "$AGENTS/$LABEL.$n.plist" ] && instances+=("$n")
done
if [ ${#instances[@]} -eq 0 ]; then
echo "No Frametop Sunshine instances here; run frametop-host-setup.sh first." >&2
exit 1
fi
IFS=$'\n' read -r -d '' -a instances < <(printf '%s\n' "${instances[@]}" | sort -n) || true
fi
failed=0
case $cmd in
on)
for n in "${instances[@]}"; do
start "$n" || failed=1
done
# Wait for each to answer (or to wait for its display), so the status means something.
for i in $(seq 1 20); do
pending=0
for n in "${instances[@]}"; do
loaded "$n" || continue
answers "$n" || [ -e "$DATA/instance-$n/.waiting" ] || pending=1
done
[ $pending = 0 ] && break
sleep 1
done
;;
off)
for n in "${instances[@]}"; do
stop "$n"
done
for i in $(seq 1 10); do
pending=0
for n in "${instances[@]}"; do
loaded "$n" && pending=1
done
[ $pending = 0 ] && break
sleep 0.5
done
;;
esac
status
exit $failed