Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·245 lines (220 loc) · 10.4 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·245 lines (220 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#!/usr/bin/env bash
# Display Share — build and install the Mac sender from source.
#
# Building from source is the easiest path for this project, not the hardest:
# a locally built app carries no quarantine attribute, so macOS launches it
# without the "Apple cannot check it" warning that the downloaded .dmg triggers.
#
# ./install.sh build + install to /Applications
# ./install.sh --prefix DIR install somewhere else
# ./install.sh --no-open don't open System Settings at the end
# ./install.sh --uninstall remove the app and its stored data
set -euo pipefail
PREFIX="/Applications"
OPEN_SETTINGS=1
UNINSTALL=0
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
while [[ $# -gt 0 ]]; do
case "$1" in
--prefix) PREFIX="$2"; shift 2 ;;
--no-open) OPEN_SETTINGS=0; shift ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
die() { printf '\033[31mError:\033[0m %s\n' "$*" >&2; exit 1; }
APP="$PREFIX/DisplayShare.app"
if [[ "$UNINSTALL" == 1 ]]; then
bold "Uninstalling Display Share"
pkill -x DisplayShare 2>/dev/null || true
pkill -x vd_helper 2>/dev/null || true
[[ -d "$APP" ]] && rm -rf "$APP" && ok "removed $APP"
rm -rf "$HOME/Library/Application Support/DisplayShare" && ok "removed stored pairings"
defaults delete in.theboringpeople.displayshare 2>/dev/null && ok "removed preferences" || true
echo
echo "macOS keeps its permission entries. To clear them fully, remove"
echo "Display Share from System Settings → Privacy & Security → Screen Recording"
echo "and → Accessibility."
exit 0
fi
# --- 1. requirements --------------------------------------------------------
bold "1/5 Checking requirements"
[[ "$(uname -s)" == "Darwin" ]] || die "the sender only runs on macOS (the receiver is the Windows app)."
MACOS_MAJOR="$(sw_vers -productVersion | cut -d. -f1)"
if (( MACOS_MAJOR < 14 )); then
die "macOS 14 or later required; this is $(sw_vers -productVersion)."
fi
ok "macOS $(sw_vers -productVersion)"
if ! xcode-select -p >/dev/null 2>&1; then
warn "Xcode command line tools missing — launching the installer"
xcode-select --install || true
die "re-run this script once the command line tools finish installing."
fi
ok "Xcode tools at $(xcode-select -p)"
if ! command -v xcodebuild >/dev/null 2>&1; then
die "xcodebuild not found. Install Xcode from the App Store, then run:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
fi
if ! command -v xcodegen >/dev/null 2>&1; then
if command -v brew >/dev/null 2>&1; then
warn "xcodegen missing — installing with Homebrew"
brew install xcodegen
else
die "xcodegen is required. Install Homebrew from https://brew.sh then:
brew install xcodegen"
fi
fi
ok "xcodegen $(xcodegen --version 2>&1 | tr -d '\n')"
# --- 2. generate + build ----------------------------------------------------
bold "2/5 Building (this takes a minute or two)"
cd "$REPO_DIR/mac"
xcodegen generate --spec project.yml >/dev/null
ok "Xcode project generated"
BUILD_LOG="$(mktemp)"
if ! xcodebuild -project DisplayShare.xcodeproj -scheme DisplayShare \
-configuration Release -derivedDataPath ./.build \
CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \
ONLY_ACTIVE_ARCH=YES \
build > "$BUILD_LOG" 2>&1; then
echo
grep -E "error:" "$BUILD_LOG" | head -20
die "build failed. Full log: $BUILD_LOG"
fi
BUILT="$REPO_DIR/mac/.build/Build/Products/Release/DisplayShare.app"
[[ -d "$BUILT" ]] || die "build reported success but $BUILT is missing."
# --- stable signing identity -------------------------------------------
#
# WHY THIS EXISTS. macOS ties permissions (Screen Recording, Accessibility) to
# an app's "designated requirement". For an AD-HOC signed app that requirement
# is the cdhash — the hash of the binary — so every rebuild is literally a
# different application and every previously granted permission is dropped.
# Measured on this project:
#
# ad-hoc designated => cdhash H"b4402bc6..." (changes each build)
# self-signed designated => identifier "..." and certificate root = H"8fa2..."
# (stable forever)
#
# So we sign with a local self-signed certificate instead. It is NOT trusted by
# Gatekeeper and does not pretend to be — it exists purely to give macOS a
# stable identity to hang permissions on, so you grant them once instead of
# after every rebuild.
IDENTITY="Display Share Local Signing"
if ! security find-certificate -c "$IDENTITY" >/dev/null 2>&1; then
bold "Creating a local signing identity (one time)"
TMPDIR_ID="$(mktemp -d)"
cat > "$TMPDIR_ID/ext.cnf" <<'CNF'
[req]
distinguished_name = dn
x509_extensions = v3
prompt = no
[dn]
CN = Display Share Local Signing
O = Display Share
[v3]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature
extendedKeyUsage = critical,codeSigning
CNF
openssl req -x509 -newkey rsa:2048 -keyout "$TMPDIR_ID/k.pem" -out "$TMPDIR_ID/c.pem" \
-days 3650 -nodes -config "$TMPDIR_ID/ext.cnf" >/dev/null 2>&1
# Legacy PBE on purpose: macOS cannot import OpenSSL 3's default PKCS#12.
openssl pkcs12 -export -inkey "$TMPDIR_ID/k.pem" -in "$TMPDIR_ID/c.pem" \
-out "$TMPDIR_ID/c.p12" -passout pass:dsl -name "$IDENTITY" \
-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 >/dev/null 2>&1
security import "$TMPDIR_ID/c.p12" -k ~/Library/Keychains/login.keychain-db \
-P dsl -T /usr/bin/codesign -A >/dev/null 2>&1
rm -rf "$TMPDIR_ID"
if security find-certificate -c "$IDENTITY" >/dev/null 2>&1; then
ok "created \"$IDENTITY\" — permissions will now survive rebuilds"
else
warn "could not create a signing identity; falling back to ad-hoc"
warn "you will have to re-grant permissions after each rebuild"
fi
fi
SIGN_WITH="-"
if security find-certificate -c "$IDENTITY" >/dev/null 2>&1; then
SIGN_WITH="$IDENTITY"
fi
# Re-sign with an EXPLICIT identifier.
#
# macOS keys TCC permissions on the code-signing identifier. Left to itself an
# ad-hoc Release build signs as "DisplayShare" (the executable name) rather than
# the bundle id, so it looks like a DIFFERENT app to macOS than any copy the
# user already granted — permission appears granted in System Settings while the
# app still reports it missing. Sign nested code first, then the bundle.
# Mark this as a source build, BEFORE signing so the signature covers it.
#
# A branch build's version number does not describe its contents: it can carry
# work newer than the latest release while reporting an older version. The
# automatic updater would then "upgrade" it to a release that has less in it, so
# it refuses to replace a bundle carrying this marker and notifies instead.
mkdir -p "$BUILT/Contents/Resources"
printf 'source %s\n' "$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)" \
> "$BUILT/Contents/Resources/build-origin.txt"
BUNDLE_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$BUILT/Contents/Info.plist")"
codesign --force --sign "$SIGN_WITH" --identifier "$BUNDLE_ID.core" \
"$BUILT/Contents/Frameworks/DisplayShareCore.framework" 2>/dev/null || true
codesign --force --sign "$SIGN_WITH" --identifier "$BUNDLE_ID.vd-helper" \
"$BUILT/Contents/MacOS/vd_helper" 2>/dev/null || true
codesign --force --sign "$SIGN_WITH" --identifier "$BUNDLE_ID" \
--entitlements "$REPO_DIR/mac/DisplayShare/DisplayShare.entitlements" "$BUILT"
SIGNED_ID="$(codesign -dvv "$BUILT" 2>&1 | awk -F= '/^Identifier=/{print $2}')"
[[ "$SIGNED_ID" == "$BUNDLE_ID" ]] || die "signing identifier is '$SIGNED_ID', expected '$BUNDLE_ID'"
if [[ "$SIGN_WITH" == "-" ]]; then
ok "signed as $SIGNED_ID (ad-hoc — permissions reset on every rebuild)"
else
ok "signed as $SIGNED_ID with \"$SIGN_WITH\" — permissions persist across rebuilds"
fi
# Native arch only, deliberately: this is your machine, so a universal binary
# would double the build time for nothing. The released .dmg is universal.
ok "built $(du -sh "$BUILT" | cut -f1) app bundle for $(uname -m)"
# --- 3. install -------------------------------------------------------------
bold "3/5 Installing to $PREFIX"
pkill -x DisplayShare 2>/dev/null || true
pkill -x vd_helper 2>/dev/null || true
mkdir -p "$PREFIX"
rm -rf "$APP"
cp -R "$BUILT" "$APP"
# A locally built app is not quarantined, but strip the attribute anyway in
# case the repo itself arrived as a downloaded zip.
xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true
ok "installed $APP"
# --- 4. permissions ---------------------------------------------------------
bold "4/5 Permissions"
echo " Display Share needs Screen Recording to capture the display it creates."
echo " It never captures your real screen — but macOS does not distinguish, so"
echo " the usual recording indicator appears."
echo
echo " Remote control (optional) additionally needs Accessibility."
echo
warn "macOS will not let a script grant these. You must click them yourself."
echo
echo " IMPORTANT: grant them AFTER this install, not before, and then avoid"
echo " re-running this script. An unsigned app is identified by the hash of its"
echo " binary, and every rebuild changes that hash — so macOS sees a brand new"
echo " app and silently drops the previous grant. Reinstalling in a loop while"
echo " trying to fix a permission problem is what keeps it broken."
echo
echo " If the entry is already listed but the app still says Not granted:"
echo " remove it with the minus button, then grant again."
if [[ "$OPEN_SETTINGS" == 1 ]]; then
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture" 2>/dev/null || true
ok "opened Privacy & Security → Screen Recording"
fi
# --- 5. done ----------------------------------------------------------------
bold "5/5 Done"
cat <<EOF
Next:
1. Launch Display Share (it lives in the menu bar, no Dock icon).
2. Grant Screen Recording when asked, then click Start.
3. On the receiver, open the Windows app — or for a quick test, open
http://localhost:8787 in a browser on this Mac.
4. Enter the 4-digit PIN shown in the menu bar.
Ports: 8787 (viewer page), 8788 (video + control).
Uninstall: ./install.sh --uninstall
EOF
open -a "$APP" 2>/dev/null && ok "launched Display Share" || true