-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
322 lines (267 loc) · 10.8 KB
/
install.sh
File metadata and controls
322 lines (267 loc) · 10.8 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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#!/bin/bash
set -euo pipefail
# remote-input-sync installer
# Syncs input source (Korean/English) between local and remote Mac over Screen Sharing
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/firejune/remote-input-sync/main/install.sh | bash -s
#
# Or download and run:
# curl -fsSL https://raw.githubusercontent.com/firejune/remote-input-sync/main/install.sh -o install.sh
# bash install.sh
REPO_RAW="https://raw.githubusercontent.com/firejune/remote-input-sync/main"
INSTALL_DIR="$HOME/bin"
ISSW_PATH="$INSTALL_DIR/issw"
TOGGLE_PATH="$INSTALL_DIR/toggle_input_sync.sh"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}▸${NC} $1"; }
ok() { echo -e "${GREEN}✔${NC} $1"; }
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
fail() { echo -e "${RED}✖${NC} $1"; exit 1; }
# Ensure stdin is from terminal (fixes curl | bash breaking read)
if [[ ! -t 0 ]]; then
exec < /dev/tty || fail "Cannot read user input. Download and run the script manually:\n curl -fsSL $REPO_RAW/install.sh -o install.sh && bash install.sh"
fi
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ remote-input-sync installer ║${NC}"
echo -e "${BOLD}║ Sync input source over Screen Sharing ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}"
echo ""
# -----------------------------------------------------------
# 1. Prerequisites
# -----------------------------------------------------------
info "Checking prerequisites..."
if [[ "$(uname)" != "Darwin" ]]; then
fail "This tool only works on macOS."
fi
if ! command -v swiftc &>/dev/null; then
fail "Swift compiler not found. Install Xcode Command Line Tools:\n xcode-select --install"
fi
if ! command -v python3 &>/dev/null; then
fail "python3 not found. It is required for Karabiner configuration."
fi
if ! command -v curl &>/dev/null; then
fail "curl not found."
fi
if [[ ! -d "$HOME/.config/karabiner" ]]; then
warn "Karabiner-Elements not found."
echo -e " Install it first: ${CYAN}https://karabiner-elements.pqrs.org${NC}"
echo -e " Then re-run this installer."
exit 1
fi
if [[ ! -f "$HOME/.config/karabiner/karabiner.json" ]]; then
fail "karabiner.json not found. Open Karabiner-Elements once to generate the default config."
fi
ok "Prerequisites OK"
# -----------------------------------------------------------
# 2. Remote host
# -----------------------------------------------------------
echo ""
info "Enter the remote Mac hostname (e.g. My-Mac-mini.local):"
read -rp " Hostname: " REMOTE_HOSTNAME
if [[ -z "$REMOTE_HOSTNAME" ]]; then
fail "Hostname is required."
fi
info "Enter the SSH username for ${BOLD}${REMOTE_HOSTNAME}${NC} (default: $(whoami)):"
read -rp " Username [$(whoami)]: " REMOTE_USER
REMOTE_USER="${REMOTE_USER:-$(whoami)}"
REMOTE_HOST="${REMOTE_USER}@${REMOTE_HOSTNAME}"
echo ""
ok "Remote: ${BOLD}${REMOTE_HOST}${NC}"
# -----------------------------------------------------------
# 3. Input toggle key
# -----------------------------------------------------------
echo ""
info "Which key do you use for input source switching?"
echo ""
echo -e " ${BOLD}1)${NC} ⌘ Cmd + Space ${YELLOW}(legacy macOS)${NC}"
echo -e " ${BOLD}2)${NC} ⌃ Ctrl + Space ${YELLOW}(default macOS)${NC}"
echo -e " ${BOLD}3)${NC} 🌐 Fn / Globe key"
echo ""
read -rp " Choose [1/2/3]: " KEY_CHOICE
case "$KEY_CHOICE" in
1) KEY_DESC="Cmd + Space"
KARABINER_FROM='{ "key_code": "spacebar", "modifiers": { "mandatory": ["command"], "optional": ["any"] } }' ;;
2) KEY_DESC="Ctrl + Space"
KARABINER_FROM='{ "key_code": "spacebar", "modifiers": { "mandatory": ["control"], "optional": ["any"] } }' ;;
3) KEY_DESC="Fn / Globe"
KARABINER_FROM='{ "apple_vendor_keyboard_key_code": "function" }' ;;
*) fail "Invalid choice. Please enter 1, 2, or 3." ;;
esac
ok "Toggle key: ${BOLD}${KEY_DESC}${NC}"
# -----------------------------------------------------------
# 4. Build issw locally
# -----------------------------------------------------------
echo ""
info "Building issw..."
mkdir -p "$INSTALL_DIR"
SWIFT_SRC=$(mktemp /tmp/issw_XXXXXX.swift)
trap 'rm -f "$SWIFT_SRC"' EXIT
if ! curl -fsSL "$REPO_RAW/src/issw.swift" -o "$SWIFT_SRC"; then
fail "Failed to download issw.swift. Check your internet connection."
fi
if ! swiftc -O "$SWIFT_SRC" -framework Carbon -o "$ISSW_PATH"; then
fail "Failed to compile issw. Check your Swift compiler."
fi
chmod +x "$ISSW_PATH"
ok "Installed: ${BOLD}${ISSW_PATH}${NC}"
# -----------------------------------------------------------
# 5. Detect input sources
# -----------------------------------------------------------
info "Detecting input sources..."
if CURRENT=$("$ISSW_PATH"); then
ok "Current input source: ${BOLD}${CURRENT}${NC}"
else
warn "Could not detect input source (may work fine after reboot)"
fi
# -----------------------------------------------------------
# 6. Install toggle script
# -----------------------------------------------------------
info "Installing toggle script..."
TOGGLE_SRC=$(mktemp /tmp/toggle_XXXXXX.sh)
if ! curl -fsSL "$REPO_RAW/toggle_input_sync.sh" -o "$TOGGLE_SRC"; then
rm -f "$TOGGLE_SRC"
fail "Failed to download toggle script."
fi
sed "s|__REMOTE_HOST__|${REMOTE_HOST}|g" "$TOGGLE_SRC" > "$TOGGLE_PATH"
rm -f "$TOGGLE_SRC"
chmod +x "$TOGGLE_PATH"
ok "Installed: ${BOLD}${TOGGLE_PATH}${NC}"
# -----------------------------------------------------------
# 7. SSH key setup
# -----------------------------------------------------------
echo ""
info "Checking SSH key authentication..."
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$REMOTE_HOST" 'echo ok' &>/dev/null; then
ok "SSH key auth already configured"
else
warn "SSH key auth not set up for ${REMOTE_HOST}"
if [[ ! -f "$HOME/.ssh/id_ed25519.pub" ]] && [[ ! -f "$HOME/.ssh/id_rsa.pub" ]]; then
info "Generating SSH key..."
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -N "" -q
fi
# Use whichever key exists
SSH_PUB_KEY=""
if [[ -f "$HOME/.ssh/id_ed25519.pub" ]]; then
SSH_PUB_KEY="$HOME/.ssh/id_ed25519.pub"
elif [[ -f "$HOME/.ssh/id_rsa.pub" ]]; then
SSH_PUB_KEY="$HOME/.ssh/id_rsa.pub"
fi
info "Copying SSH key to ${REMOTE_HOST}..."
echo -e " ${YELLOW}You may be prompted for the remote Mac's password.${NC}"
if ! ssh-copy-id -i "$SSH_PUB_KEY" "$REMOTE_HOST"; then
fail "Failed to copy SSH key. Make sure:\n 1. Remote Login is enabled on the remote Mac (System Settings → General → Sharing)\n 2. The hostname and username are correct"
fi
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$REMOTE_HOST" 'echo ok' &>/dev/null; then
ok "SSH key auth configured"
else
fail "SSH key auth verification failed. Check your credentials and try again."
fi
fi
# -----------------------------------------------------------
# 8. Build issw on remote
# -----------------------------------------------------------
echo ""
info "Building issw on remote Mac..."
# Check remote has swiftc
if ! ssh -o ConnectTimeout=5 "$REMOTE_HOST" 'command -v swiftc' &>/dev/null; then
fail "Swift compiler not found on remote Mac.\n Run on the remote Mac: xcode-select --install"
fi
if ! curl -fsSL "$REPO_RAW/src/issw.swift" | ssh "$REMOTE_HOST" 'cat > /tmp/issw_build.swift && mkdir -p ~/bin && swiftc -O /tmp/issw_build.swift -framework Carbon -o ~/bin/issw && rm /tmp/issw_build.swift'; then
fail "Failed to build issw on remote Mac."
fi
REMOTE_CURRENT=$(ssh -o ConnectTimeout=5 "$REMOTE_HOST" '~/bin/issw' 2>/dev/null || echo "unknown")
ok "Remote issw installed (current: ${BOLD}${REMOTE_CURRENT}${NC})"
# -----------------------------------------------------------
# 9. Karabiner configuration
# -----------------------------------------------------------
echo ""
info "Configuring Karabiner-Elements..."
KARABINER_CONFIG="$HOME/.config/karabiner/karabiner.json"
BACKUP="$HOME/.config/karabiner/karabiner.json.backup.$(date +%s)"
cp "$KARABINER_CONFIG" "$BACKUP"
ok "Config backed up: ${BOLD}${BACKUP##*/}${NC}"
# Build the new rule JSON
NEW_RULE=$(cat <<EOF
{
"description": "remote-input-sync: Sync input source with remote Mac (${KEY_DESC})",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": ["^com\\\\.apple\\\\.ScreenSharing$"],
"type": "frontmost_application_if"
}
],
"from": ${KARABINER_FROM},
"to": [
{
"shell_command": "${TOGGLE_PATH}"
}
],
"type": "basic"
}
]
}
EOF
)
# Insert the rule using python3 (ships with macOS)
if ! python3 - "$KARABINER_CONFIG" "$NEW_RULE" <<'PYEOF'
import json, sys
config_path = sys.argv[1]
new_rule = json.loads(sys.argv[2])
with open(config_path, 'r') as f:
config = json.load(f)
# Ensure structure exists
profiles = config.get('profiles', [])
if not profiles:
print("Error: no profiles found in karabiner.json", file=sys.stderr)
sys.exit(1)
profile = profiles[0]
complex_mods = profile.setdefault('complex_modifications', {})
rules = complex_mods.setdefault('rules', [])
# Remove existing remote-input-sync rules
rules[:] = [r for r in rules if not r.get('description', '').startswith('remote-input-sync:')]
rules.insert(0, new_rule)
with open(config_path, 'w') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
PYEOF
then
warn "Failed to update Karabiner config. Restoring backup..."
cp "$BACKUP" "$KARABINER_CONFIG"
fail "Karabiner configuration failed. You can manually add the rule."
fi
ok "Karabiner rule added"
# -----------------------------------------------------------
# 10. PATH check
# -----------------------------------------------------------
echo ""
if [[ ":$PATH:" != *":$HOME/bin:"* ]]; then
warn "\$HOME/bin is not in your PATH."
echo -e " Add this to your shell profile (~/.zshrc):"
echo -e " ${CYAN}export PATH=\"\$HOME/bin:\$PATH\"${NC}"
echo ""
fi
# -----------------------------------------------------------
# Done!
# -----------------------------------------------------------
echo ""
echo -e "${GREEN}${BOLD}✔ Installation complete!${NC}"
echo ""
echo -e " ${BOLD}How it works:${NC}"
echo -e " 1. Open Screen Sharing and connect to ${BOLD}${REMOTE_HOSTNAME}${NC}"
echo -e " 2. Press ${BOLD}${KEY_DESC}${NC} to toggle input source"
echo -e " 3. Both local and remote will switch in sync"
echo ""
echo -e " ${BOLD}To uninstall:${NC}"
echo -e " ${CYAN}rm ~/bin/issw ~/bin/toggle_input_sync.sh${NC}"
echo -e " Then remove the rule from Karabiner-Elements preferences."
echo ""