Compare commits
10 Commits
b3f59b7bf5
...
9f76d6b7f4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f76d6b7f4 | ||
|
|
ca4fddd57f | ||
|
|
c5a368aee5 | ||
|
|
b8199375a9 | ||
|
|
bab08cd8a7 | ||
|
|
36aed19169 | ||
|
|
5b59dbc2da | ||
|
|
002c27e73c | ||
|
|
3feaf1bbc3 | ||
|
|
b8b7b253bb |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ aether_native_app_config.json
|
||||
# package-lock.json
|
||||
builds/
|
||||
resources/seed_config.json
|
||||
event.env
|
||||
.playwright-mcp/
|
||||
builds.tar.gz
|
||||
|
||||
BIN
AE_app_native_Electron_2026-04-20.tar.gz
Normal file
BIN
AE_app_native_Electron_2026-04-20.tar.gz
Normal file
Binary file not shown.
261
README.md
261
README.md
@@ -18,6 +18,15 @@ This application serves as the "Native Mode" runtime for Aether podiums and devi
|
||||
SSH user on all laptops: **`speaker ready`**
|
||||
IP pattern: `192.168.32.1XX` (XX = zero-padded laptop number, e.g. 03 → `.103`). Find/replace this prefix for other onsite environments.
|
||||
|
||||
Deploy files live in `deploy/`:
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `deploy/deploy.sh` | Deploy script — handles arch detection, scp, and seed.json |
|
||||
| `deploy/devices.conf` | Laptop list: number, IP, `event_device_id` |
|
||||
| `deploy/event.env` | **Gitignored** — per-event API key and URLs (create from example) |
|
||||
| `deploy/event.env.example` | Template for `event.env` |
|
||||
|
||||
### Step 1 — Build the app (workstation)
|
||||
|
||||
```bash
|
||||
@@ -29,35 +38,66 @@ npm run package:mac
|
||||
Only rebuild if source code has changed. The `.app` bundle is identical for all Intel laptops —
|
||||
only `~/seed.json` differs per device.
|
||||
|
||||
### Step 2 — Determine target architecture and copy the .app
|
||||
> **Note:** The build will print `WARNING: Could not find icon "..." with extension ".icon"`. This
|
||||
> is cosmetic — `electron-packager` checks for several icon format variants and warns for each it
|
||||
> doesn't find. The `.icns` file is correctly embedded as `electron.icns` inside the app bundle.
|
||||
|
||||
### Step 2 — Create event.env
|
||||
|
||||
Check the target Mac's CPU if unsure:
|
||||
```bash
|
||||
ssh "speaker ready"@192.168.32.103 "uname -m"
|
||||
# x86_64 → use aether_launcher-darwin-x64 (MacBook Air 2018 and all current Intel Macs)
|
||||
# arm64 → use aether_launcher-darwin-arm64 (Apple Silicon M1/M2/M3/M4)
|
||||
cp deploy/event.env.example deploy/event.env
|
||||
# Edit deploy/event.env — fill in AETHER_API_KEY
|
||||
```
|
||||
|
||||
Copy from the workstation (replace `103` with the target IP last octet):
|
||||
Create the API key in the Aether admin panel before the show (Core → Accounts or Events →
|
||||
Devices API key section). All laptops share one key per event. Delete it after the show.
|
||||
|
||||
### Step 3 — Run the deploy script
|
||||
|
||||
```bash
|
||||
# Intel (current hardware):
|
||||
# Deploy specific laptops:
|
||||
./deploy/deploy.sh 01 02 03
|
||||
|
||||
# Deploy all laptops in devices.conf:
|
||||
./deploy/deploy.sh all
|
||||
|
||||
# Update seed.json only (no .app copy — e.g. when rotating the API key):
|
||||
./deploy/deploy.sh --seed-only all
|
||||
```
|
||||
|
||||
The script auto-detects each Mac's CPU architecture, copies the correct `.app` build, writes
|
||||
`seed.json`, and verifies. One SSH connection failure won't abort the batch — it logs and
|
||||
continues, then reports which laptops need a retry.
|
||||
|
||||
### Step 4 — Verify and launch
|
||||
|
||||
After the script completes, launch the app on each laptop and confirm it connects and shows
|
||||
the correct device name in the Launcher UI.
|
||||
|
||||
### Adding SSH key to a new laptop (first time only)
|
||||
|
||||
```bash
|
||||
ssh-copy-id "speaker ready"@192.168.32.1XX
|
||||
```
|
||||
|
||||
Run once per laptop before deploying.
|
||||
|
||||
---
|
||||
|
||||
### Manual deploy reference
|
||||
|
||||
The script covers the normal case. For one-off fixes or if the script isn't available:
|
||||
|
||||
```bash
|
||||
# Detect arch
|
||||
ssh "speaker ready"@192.168.32.103 "uname -m"
|
||||
# x86_64 → darwin-x64 | arm64 → darwin-arm64
|
||||
|
||||
# Copy .app (Intel example):
|
||||
scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.103:/Applications/aether_launcher.app
|
||||
|
||||
# Apple Silicon (future hardware):
|
||||
scp -r builds/aether_launcher-darwin-arm64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.103:/Applications/aether_launcher.app
|
||||
```
|
||||
|
||||
If `/Applications/aether_launcher.app` already exists, `scp -r` overwrites it. No need to
|
||||
remove the old version first.
|
||||
|
||||
### Step 3 — Write seed.json on the target laptop
|
||||
|
||||
The seed file lives at `~/seed.json` (`/Users/speaker ready/seed.json`) on each Mac.
|
||||
It is intentionally outside the app bundle so it can be updated without redeploying.
|
||||
|
||||
```bash
|
||||
# Write seed.json:
|
||||
ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF'
|
||||
{
|
||||
"event_device_id": "DEVICE_ID_FOR_THIS_LAPTOP",
|
||||
@@ -67,57 +107,13 @@ ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF'
|
||||
"onsite_api_base_url": null
|
||||
}
|
||||
EOF
|
||||
|
||||
# Verify:
|
||||
ssh "speaker ready"@192.168.32.103 "cat ~/seed.json"
|
||||
```
|
||||
|
||||
`event_device_id` values by laptop — see the **Device Reference** table below.
|
||||
|
||||
### Step 4 — Verify
|
||||
|
||||
```bash
|
||||
# Confirm seed.json landed correctly
|
||||
ssh "speaker ready"@192.168.32.103 "cat ~/seed.json"
|
||||
|
||||
# Confirm the .app is present
|
||||
ssh "speaker ready"@192.168.32.103 "ls /Applications/aether_launcher.app"
|
||||
```
|
||||
|
||||
Then launch the app on the laptop and confirm it connects and shows the correct device name
|
||||
in the Launcher UI.
|
||||
|
||||
### Updating seed.json only (no app reinstall)
|
||||
|
||||
If only the device config needs updating (e.g. changing `event_device_id` or `onsite_api_base_url`):
|
||||
|
||||
```bash
|
||||
ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF'
|
||||
{ ... }
|
||||
EOF
|
||||
```
|
||||
|
||||
No need to re-copy the `.app`. Restart the Electron app after writing the new seed.
|
||||
|
||||
### Deploy to multiple laptops at once
|
||||
|
||||
Repeat Steps 2–3 for each laptop, or use a loop:
|
||||
|
||||
```bash
|
||||
for OCTET in 103 104 105 106; do
|
||||
echo "=== Deploying to 192.168.32.$OCTET ==="
|
||||
scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \
|
||||
"speaker ready"@192.168.32.$OCTET:/Applications/aether_launcher.app
|
||||
done
|
||||
```
|
||||
|
||||
`seed.json` must still be written per-device (each has a unique `event_device_id`).
|
||||
|
||||
### Adding SSH key to a new laptop (first time only)
|
||||
|
||||
```bash
|
||||
ssh-copy-id "speaker ready"@192.168.32.1XX
|
||||
```
|
||||
|
||||
Run once per laptop before attempting any of the deploy steps above.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Device Reference
|
||||
@@ -202,35 +198,130 @@ explicitly coordinated across all devices.
|
||||
|
||||
The bridge is exposed to the renderer via `contextBridge`. It can be accessed in the web UI via `window.aetherNative`.
|
||||
|
||||
### Core Methods
|
||||
**Design principle:** The Electron app is a thin OS primitive layer. Business logic (which script
|
||||
runs for which file type, how to sequence operations, etc.) belongs in the SvelteKit/Svelte side
|
||||
where it can be changed without a rebuild and redeployment. Electron handlers should rarely need
|
||||
to change.
|
||||
|
||||
### File Cache
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `list_tools()` | Returns a JSON manifest of all available native functions. |
|
||||
| `launch_presentation({path, app})` | Launches a presentation with auto-focus and slideshow start. |
|
||||
| `control_presentation({app, action})` | Sends `next`, `prev`, `start`, or `stop` to active decks. |
|
||||
| `open_folder(path)` | Opens a local directory in the OS file explorer. |
|
||||
| `get_device_info()` | Returns hardware metadata (RAM, IPs, Hostname). |
|
||||
| `check_cache({cache_root, hash, hash_prefix_length?, verify_hash?})` | Checks if a file exists in the hashed cache. `verify_hash: true` re-hashes the file to confirm integrity. |
|
||||
| `download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` | Streams a file from the API into the hashed cache. Verifies SHA-256 integrity before finalizing. |
|
||||
| `copy_from_cache_to_temp({cache_root, hash, temp_root, filename, hash_prefix_length?})` | **Preferred primitive.** Copies cached file to temp dir with original filename. Returns `{ success, path }`. The Svelte caller decides what to do next. |
|
||||
| `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?, script_template?})` | Combines copy + launch. If `script_template` is provided, runs it (AppleScript or `shell:` prefixed command) instead of hardcoded extension logic. Falls back to built-in defaults when `null`. |
|
||||
|
||||
### Shell & OS
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `run_cmd({cmd, timeout?, return_stdout?})` | Async shell command execution. |
|
||||
| `run_cmd_sync({cmd})` | Synchronous shell command execution. |
|
||||
| `run_osascript(script)` | **Hardened.** Runs AppleScript via temp `.scpt` file — handles multi-line scripts and paths with spaces/special characters correctly. macOS only. |
|
||||
| `open_folder(path)` | Opens a directory in Finder / system file manager. |
|
||||
| `open_local_file_v2(path)` | Opens a file with its default OS application. |
|
||||
| `open_external({url, app?})` | Opens a URL in Chrome, Firefox, or the default browser. |
|
||||
| `kill_processes({process_name_li})` | Terminates processes by name. macOS/Linux: `pkill -f`. Windows: `taskkill /F`. |
|
||||
|
||||
### Presentations (Phase 5 — legacy specialized handlers)
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `launch_presentation({path, app?, os?})` | Platform-aware launcher. Resolves `[home]`/`[tmp]` placeholders. **Note:** uses legacy `-e` flag for AppleScript; prefer `copy_from_cache_to_temp` + `run_osascript` for new flows. |
|
||||
| `control_presentation({app, action})` | Slide navigation (`next`/`prev`/`start`/`stop`) for PowerPoint or Keynote via AppleScript. macOS only. |
|
||||
|
||||
### System Management (Phase 5)
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `get_device_config()` | Returns hydrated device config injected at startup from `seed.json` + API. |
|
||||
| `get_device_info()` | Returns OS metadata: platform, hostname, IPs, CPU count, free RAM, home/tmp paths. |
|
||||
| `window_control({action, value?})` | Electron window: maximize, minimize, restore, close, fullscreen, kiosk, devtools, reload. |
|
||||
| `set_wallpaper({path})` | Sets desktop wallpaper. macOS (AppleScript) + Linux (gsettings/Gnome). |
|
||||
| `power_control({action})` | Shutdown, reboot, or sleep. macOS + Linux. Requires sudo for shutdown/reboot. |
|
||||
| `set_display_layout({mode, configStr?})` | Mirror/extend displays via bundled `displayplacer` binary. macOS only. |
|
||||
| `manage_recording({action, options?})` | Screen recording via bundled `aperture` binary. macOS only. |
|
||||
| `update_app(args)` | **Stub.** Downloads update package but does not install. Not functional. |
|
||||
| `list_tools()` | Returns a self-describing manifest of all available bridge functions. |
|
||||
|
||||
### Example Usage (preferred composable pattern)
|
||||
|
||||
### Example Usage (UI Relay)
|
||||
```typescript
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
|
||||
// Launch a file from local cache
|
||||
await native.launch_presentation({
|
||||
path: '[tmp]/my_deck.pptx',
|
||||
app: 'powerpoint'
|
||||
// Step 1: copy the cached file to temp and get the resolved path
|
||||
const copy = await native.copy_from_cache_to_temp({
|
||||
cache_root: $ae_loc.local_file_cache_path,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
temp_root: $ae_loc.host_file_temp_path,
|
||||
filename: event_file_obj.filename
|
||||
});
|
||||
if (!copy.success) { /* handle error */ return; }
|
||||
|
||||
// Navigate slides
|
||||
await native.control_presentation({
|
||||
app: 'powerpoint',
|
||||
action: 'next'
|
||||
});
|
||||
// Step 2: run whatever script/command you want with that path
|
||||
// Option A — AppleScript (macOS):
|
||||
await native.run_osascript(`
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${copy.path}")
|
||||
delay 3
|
||||
end tell
|
||||
`);
|
||||
|
||||
// Option B — shell command:
|
||||
await native.run_cmd({ cmd: `open "${copy.path}"` });
|
||||
|
||||
// Option C — use a template from device config (data-driven, no rebuild needed):
|
||||
const template = $ae_loc.native_device?.launch_scripts?.pptx;
|
||||
if (template) {
|
||||
const script = template.replace(/\{\{path\}\}/g, copy.path);
|
||||
await native.run_osascript(script);
|
||||
}
|
||||
```
|
||||
|
||||
### Configurable Launch Scripts (no rebuild needed)
|
||||
|
||||
`launch_from_cache` and `launcher_file_cont.svelte` support per-extension script templates
|
||||
stored in `event_device.data_json.launch_scripts`. Keys are lowercase extensions (`pptx`, `key`,
|
||||
`pdf`, etc.); `default` is a catch-all. Templates use `{{path}}` as the file path placeholder.
|
||||
AppleScript strings run via `run_osascript`; prefix with `shell:` for shell commands.
|
||||
|
||||
See `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` Section 8 for full details.
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
- **Preload:** Logic defined in `src/preload/index.ts`.
|
||||
- **Handlers:** OS-level logic in `src/main/shell_handlers.ts` and `src/main/file_handlers.ts`.
|
||||
- **Handlers:** OS-level logic in `src/main/shell_handlers.ts`, `src/main/file_handlers.ts`, and `src/main/system_handlers.ts`.
|
||||
- **Types:** Shared TypeScript interfaces in `src/shared/types.ts`.
|
||||
|
||||
### Build Requirements (System Dependencies)
|
||||
|
||||
`bsdtar` (libarchive) must be present on the build host. It is used by the `postinstall` patch
|
||||
to work around a hard hang in `@electron/packager` 20 on Node 26.
|
||||
|
||||
| OS | Install |
|
||||
| --- | --- |
|
||||
| Arch Linux | `sudo pacman -S libarchive` |
|
||||
| macOS | `brew install libarchive` (or use the system bsdtar included in Xcode CLT) |
|
||||
| Ubuntu/Debian | `sudo apt install libarchive-tools` |
|
||||
|
||||
### Why bsdtar — Node 26 Packaging Bug
|
||||
|
||||
**Symptom:** `npm run package:mac` or `npm run package:linux` prints
|
||||
`Packaging app for platform ... using electron v42.x` then hangs forever (or exits 0 with no output).
|
||||
|
||||
**Root cause:** `yauzl` 2.10.0 (used by `extract-zip` inside `@electron/packager` 20) creates
|
||||
read streams that never emit `data` events under Node 26. The zip extraction call blocks
|
||||
indefinitely.
|
||||
|
||||
**Fix:** `scripts/patch-packager-unzip.js` (run automatically via `postinstall`) replaces the
|
||||
`extractElectronZip` function in `node_modules/@electron/packager/dist/unzip.js` with a
|
||||
`child_process.execSync` call to `bsdtar -xf`. `bsdtar` was chosen over `7z` because `7z`
|
||||
refuses to extract macOS `.app` bundles that contain symlinks chained through other symlinks
|
||||
(e.g. `Electron Framework.framework/Libraries → Versions/Current/Libraries`, where
|
||||
`Versions/Current` is itself a symlink).
|
||||
|
||||
**This patch is re-applied automatically on every `npm install`** via the `postinstall` hook.
|
||||
If a future release of `@electron/packager` or `extract-zip` fixes Node 26 compatibility,
|
||||
remove the `postinstall` line from `package.json` and delete `scripts/patch-packager-unzip.js`.
|
||||
|
||||
220
deploy/deploy.sh
Executable file
220
deploy/deploy.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy.sh — Deploy Aether Native Launcher to onsite Mac laptops
|
||||
#
|
||||
# USAGE:
|
||||
# ./deploy.sh <num> [num ...] Deploy to one or more laptops (e.g. 03 04 05)
|
||||
# ./deploy.sh all Deploy to all laptops in devices.conf
|
||||
# ./deploy.sh --seed-only <num> Update seed.json only — skip .app copy
|
||||
# ./deploy.sh --seed-only all
|
||||
# ./deploy.sh --build <num> [num ...] Build first (npm run package:mac), then deploy
|
||||
# ./deploy.sh --build all
|
||||
#
|
||||
# REQUIRES:
|
||||
# event.env — copy from event.env.example and fill in AETHER_API_KEY
|
||||
# builds/ — pre-built, or use --build to build before deploying
|
||||
#
|
||||
# SSH keys must already be installed on each target (run ssh-copy-id once per laptop).
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEVICES_FILE="$SCRIPT_DIR/devices.conf"
|
||||
EVENT_ENV="$SCRIPT_DIR/event.env"
|
||||
SSH_USER="speaker ready"
|
||||
BUILD_DIR="$SCRIPT_DIR/../builds"
|
||||
|
||||
# ── Argument parsing ──────────────────────────────────────────────────────────
|
||||
|
||||
SEED_ONLY=false
|
||||
BUILD_FIRST=false
|
||||
TARGETS=()
|
||||
|
||||
usage() {
|
||||
grep '^#' "$0" | grep -v '^#!/' | sed 's/^# \{0,1\}//'
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ $# -eq 0 ]]; then usage; fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--seed-only) SEED_ONLY=true ;;
|
||||
--build) BUILD_FIRST=true ;;
|
||||
--help|-h) usage ;;
|
||||
all) TARGETS+=("all") ;;
|
||||
*) TARGETS+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
||||
echo "ERROR: No targets specified."
|
||||
usage
|
||||
fi
|
||||
|
||||
# ── Load event config ─────────────────────────────────────────────────────────
|
||||
|
||||
if [[ ! -f "$EVENT_ENV" ]]; then
|
||||
echo "ERROR: $EVENT_ENV not found."
|
||||
echo " Copy event.env.example → event.env and fill in AETHER_API_KEY."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$EVENT_ENV"
|
||||
|
||||
: "${AETHER_API_KEY:?event.env must set AETHER_API_KEY}"
|
||||
: "${PRIMARY_API_BASE_URL:?event.env must set PRIMARY_API_BASE_URL}"
|
||||
: "${BACKUP_API_BASE_URL:?event.env must set BACKUP_API_BASE_URL}"
|
||||
|
||||
# Render onsite URL as JSON null or quoted string
|
||||
if [[ -n "${ONSITE_API_BASE_URL:-}" ]]; then
|
||||
ONSITE_JSON="\"$ONSITE_API_BASE_URL\""
|
||||
else
|
||||
ONSITE_JSON="null"
|
||||
fi
|
||||
|
||||
# ── Device list helpers ───────────────────────────────────────────────────────
|
||||
|
||||
# Returns "ip device_id" for a laptop number, or empty if not found
|
||||
lookup_device() {
|
||||
local num="$1"
|
||||
grep -v '^[[:space:]]*#' "$DEVICES_FILE" \
|
||||
| grep -E "^[[:space:]]*${num}[[:space:]]" \
|
||||
| awk '{print $2, $3}' \
|
||||
| head -1
|
||||
}
|
||||
|
||||
# Returns all laptop numbers from devices.conf (first column, non-comment lines)
|
||||
all_device_nums() {
|
||||
grep -v '^[[:space:]]*#' "$DEVICES_FILE" \
|
||||
| grep -v '^[[:space:]]*$' \
|
||||
| awk '{print $1}'
|
||||
}
|
||||
|
||||
# ── Deploy one laptop ─────────────────────────────────────────────────────────
|
||||
|
||||
deploy_laptop() {
|
||||
local num="$1"
|
||||
|
||||
local info
|
||||
info=$(lookup_device "$num")
|
||||
if [[ -z "$info" ]]; then
|
||||
echo " ERROR: Laptop $num not found in devices.conf"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ip device_id
|
||||
ip=$(echo "$info" | awk '{print $1}')
|
||||
device_id=$(echo "$info" | awk '{print $2}')
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════"
|
||||
echo " Laptop $num · $ip · $device_id"
|
||||
echo "══════════════════════════════════════════════"
|
||||
|
||||
# ── Copy .app ──────────────────────────────────────────────────────────
|
||||
if [[ "$SEED_ONLY" != "true" ]]; then
|
||||
echo " Detecting architecture..."
|
||||
local arch
|
||||
arch=$(ssh "$SSH_USER@$ip" "uname -m" 2>/dev/null) || {
|
||||
echo " ERROR: SSH failed for $ip — is the laptop on the network?"
|
||||
return 1
|
||||
}
|
||||
|
||||
local bundle
|
||||
case "$arch" in
|
||||
x86_64) bundle="$BUILD_DIR/aether_launcher-darwin-x64/aether_launcher.app" ;;
|
||||
arm64) bundle="$BUILD_DIR/aether_launcher-darwin-arm64/aether_launcher.app" ;;
|
||||
*)
|
||||
echo " ERROR: Unknown arch '$arch' on $ip"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ! -d "$bundle" ]]; then
|
||||
echo " ERROR: Build not found: $bundle"
|
||||
echo " Run: npm run package:mac"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " Arch: $arch → syncing $(basename "$bundle")..."
|
||||
# rsync --delete syncs contents in-place without removing the top-level .app dir.
|
||||
# This preserves the inode so macOS Aliases and Desktop shortcuts keep working.
|
||||
rsync -a --delete -e ssh "$bundle/" "$SSH_USER@$ip:/Applications/aether_launcher.app/" || {
|
||||
echo " ERROR: rsync failed."
|
||||
return 1
|
||||
}
|
||||
echo " .app synced."
|
||||
else
|
||||
echo " (--seed-only: skipping .app copy)"
|
||||
fi
|
||||
|
||||
# ── Write seed.json ────────────────────────────────────────────────────
|
||||
echo " Writing seed.json..."
|
||||
ssh "$SSH_USER@$ip" "cat > ~/seed.json" <<EOF
|
||||
{
|
||||
"event_device_id": "$device_id",
|
||||
"aether_api_key": "$AETHER_API_KEY",
|
||||
"primary_api_base_url": "$PRIMARY_API_BASE_URL",
|
||||
"backup_api_base_url": "$BACKUP_API_BASE_URL",
|
||||
"onsite_api_base_url": $ONSITE_JSON
|
||||
}
|
||||
EOF
|
||||
|
||||
# ── Verify ─────────────────────────────────────────────────────────────
|
||||
echo " Verifying..."
|
||||
ssh "$SSH_USER@$ip" "cat ~/seed.json"
|
||||
if [[ "$SEED_ONLY" != "true" ]]; then
|
||||
ssh "$SSH_USER@$ip" \
|
||||
"test -d /Applications/aether_launcher.app && echo ' ✓ .app present' || echo ' ✗ .app NOT found'"
|
||||
fi
|
||||
|
||||
echo " ✓ Laptop $num done."
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Build if requested ───────────────────────────────────────────────────────
|
||||
|
||||
if [[ "$BUILD_FIRST" == "true" ]]; then
|
||||
echo "══════════════════════════════════════════════"
|
||||
echo " Building: npm run package:mac"
|
||||
echo "══════════════════════════════════════════════"
|
||||
(cd "$SCRIPT_DIR/.." && npm run package:mac) || {
|
||||
echo "ERROR: Build failed. Aborting deploy."
|
||||
exit 1
|
||||
}
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── Expand "all" target ───────────────────────────────────────────────────────
|
||||
|
||||
EXPANDED_TARGETS=()
|
||||
for t in "${TARGETS[@]}"; do
|
||||
if [[ "$t" == "all" ]]; then
|
||||
while IFS= read -r num; do
|
||||
EXPANDED_TARGETS+=("$num")
|
||||
done < <(all_device_nums)
|
||||
else
|
||||
EXPANDED_TARGETS+=("$t")
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Run deploys ───────────────────────────────────────────────────────────────
|
||||
|
||||
FAILED=()
|
||||
for num in "${EXPANDED_TARGETS[@]}"; do
|
||||
if ! deploy_laptop "$num"; then
|
||||
FAILED+=("$num")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════"
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
echo " All done. ✓"
|
||||
else
|
||||
echo " FAILED: ${FAILED[*]}"
|
||||
echo " Re-run with just those numbers to retry."
|
||||
exit 1
|
||||
fi
|
||||
28
deploy/devices.conf
Normal file
28
deploy/devices.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
# Aether Native Launcher — Device List
|
||||
# Fields: laptop_num ip_address event_device_id [notes]
|
||||
# Blank lines and lines starting with # are ignored.
|
||||
#
|
||||
# IP pattern: 192.168.32.1XX (XX = zero-padded laptop number)
|
||||
# SSH user: "speaker ready" on all laptops
|
||||
# Find/replace 192.168.32 for other venue network prefixes.
|
||||
#
|
||||
# num ip event_device_id notes
|
||||
01 192.168.32.101 tFLL1fLQfnk
|
||||
02 192.168.32.102 rpbfunVPEzw
|
||||
03 192.168.192.203 1EPfPX8kfw8
|
||||
04 192.168.192.204 zvgyLM5yieU
|
||||
05 192.168.192.205 QOc046GoeSc
|
||||
06 192.168.192.206 2o8j6eb0L6s
|
||||
07 192.168.32.107 Oa1tlxPEVSQ
|
||||
08 192.168.32.108 fY4yznpUZ48
|
||||
09 192.168.32.109 YlgGCyjo9bY
|
||||
10 192.168.32.110 GcTnFsp1mHI
|
||||
# 11 192.168.32.111 6z88m9oEZio
|
||||
# 12 192.168.32.112 EggJqL2kWkA
|
||||
# 13 192.168.32.113 O11eckHFdVE
|
||||
# 14 192.168.32.114 reI0SecUEhI
|
||||
# 15 192.168.32.115 crozxT8mA44
|
||||
# 16 192.168.32.116 0nP4VZsvr2Q
|
||||
# 17 192.168.32.117 Gm2gNqPGzLA
|
||||
# 19 192.168.32.119 6tpukvRVugU
|
||||
# x20 192.168.32.120 rwLYnKUNd1M old 04, spare/retired
|
||||
15
deploy/event.env.example
Normal file
15
deploy/event.env.example
Normal file
@@ -0,0 +1,15 @@
|
||||
# event.env — Per-event deployment config for deploy.sh
|
||||
# Copy this file to event.env and fill in the values before deploying.
|
||||
# event.env is gitignored — never commit it (contains the API key).
|
||||
#
|
||||
# AETHER_API_KEY: shared across all laptops for this event deployment.
|
||||
# Create in Aether admin (Core → Accounts or Events → Devices API key section)
|
||||
# before the show. Delete after the show.
|
||||
#
|
||||
# ONSITE_API_BASE_URL: set to the local onsite API if running one (e.g.
|
||||
# http://192.168.32.1/api). Leave blank to use null (cloud-only mode).
|
||||
|
||||
AETHER_API_KEY="your_api_key_here"
|
||||
PRIMARY_API_BASE_URL="https://api.oneskyit.com"
|
||||
BACKUP_API_BASE_URL="https://bak-api.oneskyit.com"
|
||||
ONSITE_API_BASE_URL=""
|
||||
10
dist/main/api_client.js
vendored
10
dist/main/api_client.js
vendored
@@ -18,7 +18,7 @@ async function fetchFullConfig(seed) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
'x-no-account-id': 'bypass'
|
||||
},
|
||||
});
|
||||
if (!deviceResponse.ok) {
|
||||
@@ -29,20 +29,16 @@ async function fetchFullConfig(seed) {
|
||||
// Use 'app_base_url' as the FQDN for the site lookup
|
||||
const fqdn = deviceData.app_base_url || 'native-demo.oneskyit.com';
|
||||
// --- STEP 2: Get Site Context ---
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search`;
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search?limit=1`;
|
||||
const siteResponse = await fetch(searchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'x-account-id': deviceData.account_id_random || deviceData.account_id || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
search_query: {
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
},
|
||||
limit: 1
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
})
|
||||
});
|
||||
if (!siteResponse.ok) {
|
||||
|
||||
2
dist/main/api_client.js.map
vendored
2
dist/main/api_client.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"api_client.js","sourceRoot":"","sources":["../../src/main/api_client.ts"],"names":[],"mappings":";;AAEA,0CA4EC;AA5EM,KAAK,UAAU,eAAe,CAAC,IAAgB;IACpD,MAAM,OAAO,GAAG;QACd,IAAI,CAAC,mBAAmB;QACxB,IAAI,CAAC,oBAAoB;QACzB,IAAI,CAAC,mBAAmB;KACzB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAa,CAAC;IAE/D,IAAI,SAAS,GAAQ,IAAI,CAAC;IAE1B,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,OAAO,KAAK,CAAC,CAAC;YAEjE,oCAAoC;YACpC,MAAM,SAAS,GAAG,GAAG,OAAO,yBAAyB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5E,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC5C,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,iBAAiB,EAAE,qBAAqB;iBACzC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;YACjD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC;YAErD,qDAAqD;YACrD,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,IAAI,0BAA0B,CAAC;YAEnE,mCAAmC;YACnC,MAAM,SAAS,GAAG,GAAG,OAAO,6BAA6B,CAAC;YAC1D,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,iBAAiB,EAAE,qBAAqB;oBACxC,cAAc,EAAE,UAAU,CAAC,iBAAiB,IAAI,UAAU,CAAC,UAAU,IAAI,EAAE;iBAC5E;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,YAAY,EAAE;wBACZ,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;qBAChD;oBACD,KAAK,EAAE,CAAC;iBACT,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,+BAA+B,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAE/F,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAC;YAElD,OAAO;gBACL,GAAG,UAAU;gBACb,aAAa,EAAE,UAAU;gBACzB,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,mCAAmC;aACxE,CAAC;QAEJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,wBAAwB,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC;YACzD,SAAS,GAAG,KAAK,CAAC;YAClB,SAAS,CAAC,eAAe;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,0DAA0D,EAAE,SAAS,CAAC,CAAC;IACrF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
||||
{"version":3,"file":"api_client.js","sourceRoot":"","sources":["../../src/main/api_client.ts"],"names":[],"mappings":";;AAEA,0CAwEC;AAxEM,KAAK,UAAU,eAAe,CAAC,IAAgB;IACpD,MAAM,OAAO,GAAG;QACd,IAAI,CAAC,mBAAmB;QACxB,IAAI,CAAC,oBAAoB;QACzB,IAAI,CAAC,mBAAmB;KACzB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAa,CAAC;IAE/D,IAAI,SAAS,GAAQ,IAAI,CAAC;IAE1B,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,OAAO,KAAK,CAAC,CAAC;YAEjE,oCAAoC;YACpC,MAAM,SAAS,GAAG,GAAG,OAAO,yBAAyB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5E,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC5C,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,iBAAiB,EAAE,QAAQ;iBAC5B;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;YACjD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC;YAErD,qDAAqD;YACrD,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,IAAI,0BAA0B,CAAC;YAEnE,mCAAmC;YACnC,MAAM,SAAS,GAAG,GAAG,OAAO,qCAAqC,CAAC;YAClE,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,kBAAkB,EAAE,IAAI,CAAC,cAAc;oBACvC,cAAc,EAAE,UAAU,CAAC,iBAAiB,IAAI,UAAU,CAAC,UAAU,IAAI,EAAE;iBAC5E;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;iBAChD,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,+BAA+B,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAE/F,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAC;YAElD,OAAO;gBACL,GAAG,UAAU;gBACb,aAAa,EAAE,UAAU;gBACzB,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,mCAAmC;aACxE,CAAC;QAEJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,wBAAwB,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC;YACzD,SAAS,GAAG,KAAK,CAAC;YAClB,SAAS,CAAC,eAAe;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,0DAA0D,EAAE,SAAS,CAAC,CAAC;IACrF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
||||
85
dist/main/file_handlers.js
vendored
85
dist/main/file_handlers.js
vendored
@@ -100,8 +100,7 @@ function registerFileHandlers() {
|
||||
method: 'get', url, responseType: 'stream',
|
||||
headers: {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id || '',
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
'x-account-id': account_id || ''
|
||||
}
|
||||
});
|
||||
const writer = fs.createWriteStream(tmp_path);
|
||||
@@ -131,20 +130,65 @@ function registerFileHandlers() {
|
||||
endpoints_in_progress = endpoints_in_progress.filter(e => e !== url);
|
||||
}
|
||||
});
|
||||
electron_1.ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2 }) => {
|
||||
electron_1.ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, script_template = null }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = (0, file_utils_1.expandPath)(temp_root);
|
||||
const target = path.join(expanded_temp, filename);
|
||||
console.log(`Native: Launching from Cache -> ${filename}`);
|
||||
if (!fs.existsSync(source)) {
|
||||
return { success: false, error: `File not in cache: ${hash}` };
|
||||
}
|
||||
if (!fs.existsSync(expanded_temp))
|
||||
fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
// 1. Copy the file to temp folder with original name
|
||||
fs.copyFileSync(source, target);
|
||||
// 2. Determine file type
|
||||
// 2a. Data-driven script override (no rebuild needed for script changes).
|
||||
// Set via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
// Format: AppleScript string with {{path}} placeholder, OR "shell:<cmd> {{path}}"
|
||||
if (script_template) {
|
||||
const resolved = script_template.replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell script for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
(0, child_process_1.exec)(cmd, (err, stdout, stderr) => {
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else
|
||||
resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Treat as AppleScript — write to temp .scpt file (same hardened approach used below)
|
||||
console.log(`Native: Running custom AppleScript for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
}
|
||||
catch (e) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
if (err)
|
||||
resolve_fn({ success: false, error: err.message });
|
||||
else
|
||||
resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
// 2b. Determine file type (legacy hardcoded launch logic — used when no script_template provided)
|
||||
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
||||
const is_pres = ['pptx', 'ppt', 'key', 'pdf', 'odp'].includes(ext);
|
||||
// 3. Optimized Launch (LibreOffice / AppleScript)
|
||||
// 3. Hardcoded launch (legacy — still the default when no script_template is configured)
|
||||
if (is_pres) {
|
||||
if (os.platform() === 'linux') {
|
||||
console.log(`Native: Launching LibreOffice (--impress) for ${target}`);
|
||||
@@ -174,8 +218,10 @@ function registerFileHandlers() {
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
delay 3
|
||||
end tell
|
||||
tell application "System Events"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
@@ -214,5 +260,30 @@ function registerFileHandlers() {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
// Thin primitive: copy a cached file to the temp directory with its original filename,
|
||||
// then return the resolved path. The caller (Svelte side) decides what to do next —
|
||||
// run_osascript, run_cmd, open_local_file, etc.
|
||||
//
|
||||
// This is the preferred building block for custom launch flows. Use launch_from_cache
|
||||
// when the built-in hardcoded logic is sufficient; use copy_from_cache_to_temp when
|
||||
// you want full control over what happens after the file lands in temp.
|
||||
electron_1.ipcMain.handle('native:copy-from-cache-to-temp', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2 }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
if (!fs.existsSync(source)) {
|
||||
return { success: false, error: `File not in cache: ${hash}` };
|
||||
}
|
||||
const expanded_temp = (0, file_utils_1.expandPath)(temp_root);
|
||||
const target = path.join(expanded_temp, filename);
|
||||
if (!fs.existsSync(expanded_temp))
|
||||
fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
fs.copyFileSync(source, target);
|
||||
console.log(`Native: Copied from cache to temp -> ${target}`);
|
||||
return { success: true, path: target };
|
||||
}
|
||||
catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=file_handlers.js.map
|
||||
2
dist/main/file_handlers.js.map
vendored
2
dist/main/file_handlers.js.map
vendored
File diff suppressed because one or more lines are too long
70
dist/main/shell_handlers.js
vendored
70
dist/main/shell_handlers.js
vendored
@@ -36,6 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerShellHandlers = registerShellHandlers;
|
||||
const electron_1 = require("electron");
|
||||
const child_process_1 = require("child_process");
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const os = __importStar(require("os"));
|
||||
const file_utils_1 = require("./file_utils");
|
||||
function registerShellHandlers() {
|
||||
@@ -65,10 +67,29 @@ function registerShellHandlers() {
|
||||
electron_1.ipcMain.handle('native:run-osascript', async (event, script) => {
|
||||
if (os.platform() !== 'darwin')
|
||||
return { success: false, error: 'AppleScript is only available on macOS' };
|
||||
const escapedScript = script.replace(/"/g, '\"');
|
||||
const cmd = `osascript -e "${escapedScript}"`;
|
||||
// HARDENED: Write script to a temp .scpt file rather than passing inline via -e.
|
||||
// The old -e approach (`osascript -e "..."`) has two fatal flaws:
|
||||
// 1. It breaks on multi-line scripts.
|
||||
// 2. It breaks on paths containing spaces or special characters (quotes, parens, etc.)
|
||||
// Writing to a file sidesteps both — no shell escaping needed at all.
|
||||
// The .scpt file is deleted immediately after execution (success or failure).
|
||||
// Worst case on crash: a stale .scpt in /tmp, cleared on next OS reboot.
|
||||
//
|
||||
// LEGACY (removed): const cmd = `osascript -e "${script.replace(/"/g, '\\"')}"`;
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_osa_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
(0, child_process_1.exec)(cmd, (error, stdout, stderr) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script.trim());
|
||||
}
|
||||
catch (e) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (error, stdout, stderr) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
@@ -113,28 +134,39 @@ function registerShellHandlers() {
|
||||
let script = '';
|
||||
if (appType === 'keynote') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`.trim();
|
||||
}
|
||||
else if (appType === 'powerpoint') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`;
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`.trim();
|
||||
}
|
||||
if (script) {
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
(0, child_process_1.exec)(`osascript -e "${escapedScript}"`, (err, stdout, stderr) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script);
|
||||
}
|
||||
catch (e) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
(0, child_process_1.exec)(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try {
|
||||
fs.unlinkSync(tmp_script_path);
|
||||
}
|
||||
catch { }
|
||||
if (err)
|
||||
resolve({ success: false, error: err.message });
|
||||
else
|
||||
|
||||
2
dist/main/shell_handlers.js.map
vendored
2
dist/main/shell_handlers.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/preload/index.js
vendored
1
dist/preload/index.js
vendored
@@ -14,6 +14,7 @@ electron_1.contextBridge.exposeInMainWorld('aetherNative', {
|
||||
open_local_file_v2: (path) => electron_1.ipcRenderer.invoke('native:open-local-file-v2', path),
|
||||
check_cache: (args) => electron_1.ipcRenderer.invoke('native:check-cache', args),
|
||||
download_to_cache: (args) => electron_1.ipcRenderer.invoke('native:download-to-cache', args),
|
||||
copy_from_cache_to_temp: (args) => electron_1.ipcRenderer.invoke('native:copy-from-cache-to-temp', args),
|
||||
launch_from_cache: (args) => electron_1.ipcRenderer.invoke('native:launch-from-cache', args),
|
||||
launch_presentation: (args) => electron_1.ipcRenderer.invoke('native:launch-presentation', args),
|
||||
control_presentation: (args) => electron_1.ipcRenderer.invoke('native:control-presentation', args),
|
||||
|
||||
2
dist/preload/index.js.map
vendored
2
dist/preload/index.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preload/index.ts"],"names":[],"mappings":";;AAAA,uCAAsD;AAEtD,wBAAa,CAAC,iBAAiB,CAAC,cAAc,EAAE;IAC9C,eAAe,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAC5D,iBAAiB,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAChE,OAAO,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,SAAS,CAAC;IAC5C,eAAe,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAE5D,WAAW,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC;IAC7E,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC;IAClE,YAAY,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC;IAC5E,aAAa,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACrF,cAAc,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC;IAChF,kBAAkB,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,2BAA2B,EAAE,IAAI,CAAC;IAE3F,WAAW,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC;IAC1E,iBAAiB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC;IACtF,iBAAiB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC;IACtF,mBAAmB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,4BAA4B,EAAE,IAAI,CAAC;IAC1F,oBAAoB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,6BAA6B,EAAE,IAAI,CAAC;IAC5F,UAAU,EAAE,GAAG,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAEzD,uBAAuB;IACvB,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;IAC9E,UAAU,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,CAAC;IACxE,cAAc,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,yBAAyB,EAAE,IAAI,CAAC;IACpF,kBAAkB,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,2BAA2B,EAAE,IAAI,CAAC;IACxF,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;IAC9E,aAAa,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,sBAAW,CAAC,MAAM,CAAC,sBAAsB,EAAE,IAAI,CAAC;CAC/E,CAAC,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preload/index.ts"],"names":[],"mappings":"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"}
|
||||
52
documentation/TODO_AGENTS.md
Normal file
52
documentation/TODO_AGENTS.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Native App Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable - ongoing development.
|
||||
|
||||
## Current Investigation
|
||||
- This started as an API contract review for the native Electron bootstrap path and expanded into a packaging/runtime issue after the deploy step stopped producing bundles.
|
||||
- We now know the API side was not the root cause. The bootstrap request shape in `src/main/api_client.ts` was wrong and has been corrected.
|
||||
- The packaging blocker has been diagnosed and fixed (see below).
|
||||
|
||||
## What Was Fixed
|
||||
- Updated the native bootstrap flow to use the direct `site_domain/search` request body expected by API V3.
|
||||
- Standardized the account-bypass header to `x-no-account-id: bypass` where that narrow bypass is intended.
|
||||
- Removed a redundant `x-no-account-id` header from file download calls.
|
||||
- Rewrote the device lookup smoke test so it validates the real two-step bootstrap path end to end.
|
||||
- Upgraded Electron from 34.x to 42.0.1.
|
||||
- Replaced deprecated `electron-packager` with `@electron/packager` 20.0.0.
|
||||
- Added a `package:linux` smoke test path so packaging failures can be isolated from macOS-specific behavior.
|
||||
- **Fixed packaging hang on Node 26:** `yauzl` 2.10.0 (used by `extract-zip` in `@electron/packager`) emits no `data` events on Node 26 streams, causing zip extraction to hang indefinitely. Fix: patched `@electron/packager/dist/unzip.js` to use `7z` (system binary) instead of `extract-zip`. Patch is re-applied on every `npm install` via the `postinstall` script at `scripts/patch-packager-unzip.js`.
|
||||
|
||||
## Verified So Far
|
||||
- `npm run dev` works once the Electron binary is present locally.
|
||||
- Manual Electron cache extraction restored a runnable checkout on this machine.
|
||||
- API validation confirmed the backend responds correctly for:
|
||||
- `event_device/{id}` lookup
|
||||
- `site_domain/search?limit=1` with the direct `SearchQuery` body
|
||||
- The returned `site_domain.account_id` matches the device account context in the verified bootstrap flow.
|
||||
- The SvelteKit frontend bootstrap path already follows the correct API contract and does not need the same fix.
|
||||
- **`npm run package:linux` now produces `builds/aether_launcher-linux-x64/`** with a complete bundle (confirmed 2026-05-11).
|
||||
- **`npm run package:mac` now produces `builds/aether_launcher-darwin-x64/` and `builds/aether_launcher-darwin-arm64/`** with `aether_launcher.app` inside each (confirmed 2026-05-11). Initial fix used `7z` but it refused chained symlinks inside macOS framework bundles; switched to `bsdtar` (libarchive) which handles both Linux and macOS zips correctly.
|
||||
- `deploy/deploy.sh` output directory names (`aether_launcher-darwin-x64`, `aether_launcher-darwin-arm64`) match packager output — no script changes needed.
|
||||
|
||||
## Remaining Items
|
||||
1. Test that the packaged Linux binary runs end-to-end against the dev API.
|
||||
2. Document that `bsdtar` (libarchive) must be present on the build host — new build-time dependency. On Arch: `sudo pacman -S libarchive`.
|
||||
|
||||
## Root Cause Summary (Packaging Hang)
|
||||
- **Tool chain:** Node 26.1.0 + `@electron/packager` 20.0.0 + `extract-zip` 2.0.1 + `yauzl` 2.10.0
|
||||
- **Symptom:** `npm run package:linux` exits 0 but produces no output. Debug log shows it starts extraction but never finishes.
|
||||
- **Root cause:** `yauzl` opens a read stream for the first zip entry, but on Node 26, no `data` events are ever emitted on that stream. The `pipeline(readStream, writeStream)` call in `extract-zip` blocks forever.
|
||||
- **Fix:** Replace the one-liner `extractElectronZip` function in `node_modules/@electron/packager/dist/unzip.js` with a `child_process.execSync` call to `7z x`. A `postinstall` npm script re-applies this patch after each `npm install`.
|
||||
- **Build-time dependency:** `p7zip` (provides `/usr/bin/7z`) must be installed on the build host. On Arch: `pacman -S p7zip`.
|
||||
|
||||
## References
|
||||
- Electron 42 release notes: https://www.electronjs.org/blog/electron-42-0
|
||||
- Related Electron packaging discussion: https://github.com/aaddrick/claude-desktop-debian/pull/587
|
||||
- Electron packaging/runtime change reference: https://github.com/electron/electron/pull/49328
|
||||
- yauzl Node 26 stream issue: `yauzl` 2.10.0 uses legacy Node streams (streams1 style); Node 26 changed stream internal behavior so `openReadStream` returns a stream that never emits `data` without a proper pipeline consumer.
|
||||
|
||||
## Notes
|
||||
- Was on Electron 34.
|
||||
- The problem is not the backend API keys or the frontend site bootstrap flow.
|
||||
- The packaging fix is a node_modules patch, not upstream. If `@electron/packager` or `extract-zip` releases a Node 26-compatible version, the `postinstall` script should be removed.
|
||||
Binary file not shown.
0
documentation/old_custom_OSIT_Master_Key_app/oneskyit-master-key-da2e467baf22/.gitignore
vendored
Normal file
0
documentation/old_custom_OSIT_Master_Key_app/oneskyit-master-key-da2e467baf22/.gitignore
vendored
Normal file
@@ -0,0 +1,567 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3C278DA7225CF5C6004FFC53 /* masterkey.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3C41339F2227E09B0041136E /* masterkey.app */; };
|
||||
3C39A72F2267ABA90061367B /* WordMacAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C39A72E2267ABA90061367B /* WordMacAssistant.swift */; };
|
||||
3C39A7312267ACCF0061367B /* AcrobatWindowsAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C39A7302267ACCF0061367B /* AcrobatWindowsAssistant.swift */; };
|
||||
3C39A7332267ACF60061367B /* Windows Application Names in Resources */ = {isa = PBXBuildFile; fileRef = 3C39A7322267ACF60061367B /* Windows Application Names */; };
|
||||
3C39A7352267AF040061367B /* WindowsVideoAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C39A7342267AF040061367B /* WindowsVideoAssistant.swift */; };
|
||||
3C39A739226A347A0061367B /* ExcelAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C39A738226A347A0061367B /* ExcelAssistant.swift */; };
|
||||
3C4133A32227E09B0041136E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133A22227E09B0041136E /* AppDelegate.swift */; };
|
||||
3C4133A52227E09F0041136E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C4133A42227E09F0041136E /* Assets.xcassets */; };
|
||||
3C4133A82227E09F0041136E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3C4133A62227E09F0041136E /* MainMenu.xib */; };
|
||||
3C4133B42227E09F0041136E /* masterkeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133B32227E09F0041136E /* masterkeyTests.swift */; };
|
||||
3C4133C02227E1B70041136E /* PowerPointMacAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133BF2227E1B70041136E /* PowerPointMacAssistant.swift */; };
|
||||
3C4133C22227E1C90041136E /* PowerPointWindowsAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133C12227E1C90041136E /* PowerPointWindowsAssistant.swift */; };
|
||||
3C4133C42227E1D60041136E /* KeynoteAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133C32227E1D60041136E /* KeynoteAssistant.swift */; };
|
||||
3C4133C62227E1E50041136E /* LegacyKeynoteAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133C52227E1E40041136E /* LegacyKeynoteAssistant.swift */; };
|
||||
3C4133C82227E2010041136E /* AcrobatAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133C72227E2010041136E /* AcrobatAssistant.swift */; };
|
||||
3C4133CA2227E2960041136E /* ApplicationAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4133C92227E2960041136E /* ApplicationAssistant.swift */; };
|
||||
3C88C06A225DADDD0048E01A /* VideoAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C88C069225DADDD0048E01A /* VideoAssistant.swift */; };
|
||||
3CE21AD62259B3EE00F666B9 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE21AD52259B3EE00F666B9 /* Utilities.swift */; };
|
||||
3CE21AD92259B4A000F666B9 /* LegacyUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CE21AD82259B4A000F666B9 /* LegacyUtilities.m */; };
|
||||
3CE21ADC225A674800F666B9 /* LibreOfficeAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE21ADB225A674800F666B9 /* LibreOfficeAssistant.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
3C4133B02227E09F0041136E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3C4133972227E09B0041136E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3C41339E2227E09B0041136E;
|
||||
remoteInfo = masterkey;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
3CE0D699224155AE00E7FC1C /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /Users/Ian/Desktop;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
3C278DA7225CF5C6004FFC53 /* masterkey.app in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3C39A72E2267ABA90061367B /* WordMacAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordMacAssistant.swift; sourceTree = "<group>"; };
|
||||
3C39A7302267ACCF0061367B /* AcrobatWindowsAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcrobatWindowsAssistant.swift; sourceTree = "<group>"; };
|
||||
3C39A7322267ACF60061367B /* Windows Application Names */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Windows Application Names"; sourceTree = "<group>"; };
|
||||
3C39A7342267AF040061367B /* WindowsVideoAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsVideoAssistant.swift; sourceTree = "<group>"; };
|
||||
3C39A738226A347A0061367B /* ExcelAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExcelAssistant.swift; sourceTree = "<group>"; };
|
||||
3C41339F2227E09B0041136E /* masterkey.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = masterkey.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C4133A22227E09B0041136E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
3C4133A42227E09F0041136E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
3C4133A72227E09F0041136E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
3C4133A92227E09F0041136E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
3C4133AA2227E09F0041136E /* masterkey.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = masterkey.entitlements; sourceTree = "<group>"; };
|
||||
3C4133AF2227E09F0041136E /* masterkeyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = masterkeyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C4133B32227E09F0041136E /* masterkeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = masterkeyTests.swift; sourceTree = "<group>"; };
|
||||
3C4133B52227E09F0041136E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
3C4133BF2227E1B70041136E /* PowerPointMacAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerPointMacAssistant.swift; sourceTree = "<group>"; };
|
||||
3C4133C12227E1C90041136E /* PowerPointWindowsAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerPointWindowsAssistant.swift; sourceTree = "<group>"; };
|
||||
3C4133C32227E1D60041136E /* KeynoteAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeynoteAssistant.swift; sourceTree = "<group>"; };
|
||||
3C4133C52227E1E40041136E /* LegacyKeynoteAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyKeynoteAssistant.swift; sourceTree = "<group>"; };
|
||||
3C4133C72227E2010041136E /* AcrobatAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcrobatAssistant.swift; sourceTree = "<group>"; };
|
||||
3C4133C92227E2960041136E /* ApplicationAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationAssistant.swift; sourceTree = "<group>"; };
|
||||
3C88C069225DADDD0048E01A /* VideoAssistant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoAssistant.swift; sourceTree = "<group>"; };
|
||||
3CE21AD52259B3EE00F666B9 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
3CE21AD72259B4A000F666B9 /* masterkey-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "masterkey-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
3CE21AD82259B4A000F666B9 /* LegacyUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LegacyUtilities.m; sourceTree = "<group>"; };
|
||||
3CE21ADA2259B5B900F666B9 /* LegacyUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LegacyUtilities.h; sourceTree = "<group>"; };
|
||||
3CE21ADB225A674800F666B9 /* LibreOfficeAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreOfficeAssistant.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
3C41339C2227E09B0041136E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3C4133AC2227E09F0041136E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3C4133962227E09B0041136E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C4133A12227E09B0041136E /* masterkey */,
|
||||
3C4133B22227E09F0041136E /* masterkeyTests */,
|
||||
3C4133A02227E09B0041136E /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C4133A02227E09B0041136E /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C41339F2227E09B0041136E /* masterkey.app */,
|
||||
3C4133AF2227E09F0041136E /* masterkeyTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C4133A12227E09B0041136E /* masterkey */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C4133BE2227E0B90041136E /* ApplicationAssistants */,
|
||||
3C4133C92227E2960041136E /* ApplicationAssistant.swift */,
|
||||
3C4133A22227E09B0041136E /* AppDelegate.swift */,
|
||||
3C4133A42227E09F0041136E /* Assets.xcassets */,
|
||||
3C4133A62227E09F0041136E /* MainMenu.xib */,
|
||||
3C4133A92227E09F0041136E /* Info.plist */,
|
||||
3C4133AA2227E09F0041136E /* masterkey.entitlements */,
|
||||
3CE21AD52259B3EE00F666B9 /* Utilities.swift */,
|
||||
3CE21ADA2259B5B900F666B9 /* LegacyUtilities.h */,
|
||||
3CE21AD82259B4A000F666B9 /* LegacyUtilities.m */,
|
||||
3CE21AD72259B4A000F666B9 /* masterkey-Bridging-Header.h */,
|
||||
3C39A7322267ACF60061367B /* Windows Application Names */,
|
||||
);
|
||||
path = masterkey;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C4133B22227E09F0041136E /* masterkeyTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C4133B32227E09F0041136E /* masterkeyTests.swift */,
|
||||
3C4133B52227E09F0041136E /* Info.plist */,
|
||||
);
|
||||
path = masterkeyTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C4133BE2227E0B90041136E /* ApplicationAssistants */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C4133BF2227E1B70041136E /* PowerPointMacAssistant.swift */,
|
||||
3C4133C12227E1C90041136E /* PowerPointWindowsAssistant.swift */,
|
||||
3C39A72E2267ABA90061367B /* WordMacAssistant.swift */,
|
||||
3C4133C32227E1D60041136E /* KeynoteAssistant.swift */,
|
||||
3C4133C52227E1E40041136E /* LegacyKeynoteAssistant.swift */,
|
||||
3C4133C72227E2010041136E /* AcrobatAssistant.swift */,
|
||||
3C39A7302267ACCF0061367B /* AcrobatWindowsAssistant.swift */,
|
||||
3CE21ADB225A674800F666B9 /* LibreOfficeAssistant.swift */,
|
||||
3C88C069225DADDD0048E01A /* VideoAssistant.swift */,
|
||||
3C39A7342267AF040061367B /* WindowsVideoAssistant.swift */,
|
||||
3C39A738226A347A0061367B /* ExcelAssistant.swift */,
|
||||
);
|
||||
path = ApplicationAssistants;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3C41339E2227E09B0041136E /* masterkey */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3C4133B82227E09F0041136E /* Build configuration list for PBXNativeTarget "masterkey" */;
|
||||
buildPhases = (
|
||||
3C41339B2227E09B0041136E /* Sources */,
|
||||
3C41339C2227E09B0041136E /* Frameworks */,
|
||||
3C41339D2227E09B0041136E /* Resources */,
|
||||
3CE0D699224155AE00E7FC1C /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = masterkey;
|
||||
productName = masterkey;
|
||||
productReference = 3C41339F2227E09B0041136E /* masterkey.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
3C4133AE2227E09F0041136E /* masterkeyTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3C4133BB2227E09F0041136E /* Build configuration list for PBXNativeTarget "masterkeyTests" */;
|
||||
buildPhases = (
|
||||
3C4133AB2227E09F0041136E /* Sources */,
|
||||
3C4133AC2227E09F0041136E /* Frameworks */,
|
||||
3C4133AD2227E09F0041136E /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
3C4133B12227E09F0041136E /* PBXTargetDependency */,
|
||||
);
|
||||
name = masterkeyTests;
|
||||
productName = masterkeyTests;
|
||||
productReference = 3C4133AF2227E09F0041136E /* masterkeyTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
3C4133972227E09B0041136E /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1010;
|
||||
ORGANIZATIONNAME = "One Sky IT";
|
||||
TargetAttributes = {
|
||||
3C41339E2227E09B0041136E = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
3C4133AE2227E09F0041136E = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
TestTargetID = 3C41339E2227E09B0041136E;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 3C41339A2227E09B0041136E /* Build configuration list for PBXProject "masterkey" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 3C4133962227E09B0041136E;
|
||||
productRefGroup = 3C4133A02227E09B0041136E /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3C41339E2227E09B0041136E /* masterkey */,
|
||||
3C4133AE2227E09F0041136E /* masterkeyTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
3C41339D2227E09B0041136E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3C4133A52227E09F0041136E /* Assets.xcassets in Resources */,
|
||||
3C4133A82227E09F0041136E /* MainMenu.xib in Resources */,
|
||||
3C39A7332267ACF60061367B /* Windows Application Names in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3C4133AD2227E09F0041136E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
3C41339B2227E09B0041136E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3C4133CA2227E2960041136E /* ApplicationAssistant.swift in Sources */,
|
||||
3C4133C22227E1C90041136E /* PowerPointWindowsAssistant.swift in Sources */,
|
||||
3C4133A32227E09B0041136E /* AppDelegate.swift in Sources */,
|
||||
3C4133C42227E1D60041136E /* KeynoteAssistant.swift in Sources */,
|
||||
3C39A739226A347A0061367B /* ExcelAssistant.swift in Sources */,
|
||||
3C39A72F2267ABA90061367B /* WordMacAssistant.swift in Sources */,
|
||||
3C4133C02227E1B70041136E /* PowerPointMacAssistant.swift in Sources */,
|
||||
3CE21AD92259B4A000F666B9 /* LegacyUtilities.m in Sources */,
|
||||
3C4133C62227E1E50041136E /* LegacyKeynoteAssistant.swift in Sources */,
|
||||
3C39A7312267ACCF0061367B /* AcrobatWindowsAssistant.swift in Sources */,
|
||||
3CE21AD62259B3EE00F666B9 /* Utilities.swift in Sources */,
|
||||
3C39A7352267AF040061367B /* WindowsVideoAssistant.swift in Sources */,
|
||||
3C4133C82227E2010041136E /* AcrobatAssistant.swift in Sources */,
|
||||
3C88C06A225DADDD0048E01A /* VideoAssistant.swift in Sources */,
|
||||
3CE21ADC225A674800F666B9 /* LibreOfficeAssistant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3C4133AB2227E09F0041136E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3C4133B42227E09F0041136E /* masterkeyTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
3C4133B12227E09F0041136E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3C41339E2227E09B0041136E /* masterkey */;
|
||||
targetProxy = 3C4133B02227E09F0041136E /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
3C4133A62227E09F0041136E /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3C4133A72227E09F0041136E /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
3C4133B62227E09F0041136E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3C4133B72227E09F0041136E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3C4133B92227E09F0041136E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = masterkey/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.oneskyit.masterkey;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "masterkey/masterkey-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3C4133BA2227E09F0041136E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = masterkey/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.oneskyit.masterkey;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "masterkey/masterkey-Bridging-Header.h";
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3C4133BC2227E09F0041136E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = masterkeyTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.oneskyit.masterkeyTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/masterkey.app/Contents/MacOS/masterkey";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3C4133BD2227E09F0041136E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = masterkeyTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.oneskyit.masterkeyTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/masterkey.app/Contents/MacOS/masterkey";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
3C41339A2227E09B0041136E /* Build configuration list for PBXProject "masterkey" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C4133B62227E09F0041136E /* Debug */,
|
||||
3C4133B72227E09F0041136E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3C4133B82227E09F0041136E /* Build configuration list for PBXNativeTarget "masterkey" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C4133B92227E09F0041136E /* Debug */,
|
||||
3C4133BA2227E09F0041136E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3C4133BB2227E09F0041136E /* Build configuration list for PBXNativeTarget "masterkeyTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C4133BC2227E09F0041136E /* Debug */,
|
||||
3C4133BD2227E09F0041136E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 3C4133972227E09B0041136E /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:masterkey.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>masterkey.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,275 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
//Open/Close
|
||||
//quicktime - MP4
|
||||
//Text
|
||||
//Doc
|
||||
|
||||
import Cocoa
|
||||
import OSAKit
|
||||
|
||||
enum ApplicationNameConstants: String {
|
||||
case PowerPointMac = "Microsoft PowerPoint"
|
||||
case PowerPointWin = "Microsoft Office PowerPoint"
|
||||
case Keynote = "Keynote"
|
||||
case LibreOffice = "LibreOffice"
|
||||
case AdobeAcrobat = "Adobe Acrobat Reader DC"
|
||||
case GoogleChrome = "Google Chrome"
|
||||
case VLC = "VLC"
|
||||
}
|
||||
|
||||
extension ApplicationNameConstants: CaseIterable {}
|
||||
|
||||
extension String {
|
||||
func fileName() -> String {
|
||||
return NSURL(fileURLWithPath: self).deletingPathExtension?.lastPathComponent ?? ""
|
||||
}
|
||||
|
||||
func fileExtension() -> String {
|
||||
return NSURL(fileURLWithPath: self).pathExtension ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var assistant:ApplicationAssistant!
|
||||
|
||||
var openingFile = false
|
||||
|
||||
//No window
|
||||
//@IBOutlet weak var window: NSWindow!
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
if !openingFile {
|
||||
NSLog("Not opening a file. Displaying setup dialog...")
|
||||
if setupDialog() {
|
||||
NSLog("Proceeding to trigger all security warnings...")
|
||||
if triggerSecurity() {
|
||||
NSLog("Presentation applications have been opened..")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSLog("Terminating applicaiton...")
|
||||
terminateApplication()
|
||||
}
|
||||
|
||||
func terminateApplication() {
|
||||
NSLog("Terminating...")
|
||||
NSApplication.shared.terminate(self)
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
|
||||
}
|
||||
|
||||
func application(_ sender: NSApplication, openFile filename: String) -> Bool {
|
||||
NSLog("Version 2.0")
|
||||
|
||||
openingFile = true
|
||||
|
||||
//Used to open a group of files
|
||||
//let first_filename = filenames[0];
|
||||
|
||||
//open a single file
|
||||
let filename_extension = String(filename.fileExtension().lowercased());
|
||||
|
||||
NSLog("Filename Extension: \(filename_extension)")
|
||||
|
||||
var assistantFlag = false
|
||||
|
||||
//Args to pass to application - not currently used
|
||||
//var args = ""
|
||||
|
||||
//once we are ready with a single presentation file (lowercase, valid ext) get into the switch
|
||||
switch filename_extension {
|
||||
case "pptx",
|
||||
"ppt",
|
||||
"pptmac",
|
||||
"pptxmac":
|
||||
NSLog("Mac PPT File...");
|
||||
assistant = PowerPointMacAssistant();
|
||||
case "pptxwin",
|
||||
"pptwin":
|
||||
NSLog("Windows PPT File...")
|
||||
assistant = PowerPointWindowsAssistant();
|
||||
case "pdf",
|
||||
"pdfmac":
|
||||
NSLog("PDF File...")
|
||||
//args = "pagemode=FullScreen"
|
||||
assistant = AcrobatAssistant();
|
||||
case "pdfwin":
|
||||
NSLog("PDF Windows File...")
|
||||
assistant = AcrobatWindowsAssistant();
|
||||
case "key":
|
||||
NSLog("Keynote File...")
|
||||
assistant = KeynoteAssistant();
|
||||
case "odp":
|
||||
NSLog("LibreOffice File...")
|
||||
assistant = LibreOfficeAssistant();
|
||||
case "mp4",
|
||||
"mkv",
|
||||
"mov",
|
||||
"mpeg",
|
||||
"avi",
|
||||
"flv",
|
||||
"ogg",
|
||||
"mp3",
|
||||
"ogv":
|
||||
NSLog("Video (VLC) File...")
|
||||
assistant = VideoAssistant();
|
||||
case "wmv":
|
||||
NSLog("Windows Video (VLC) File...")
|
||||
assistant = WindowsVideoAssistant();
|
||||
case "doc",
|
||||
"docx":
|
||||
NSLog("Microsoft Word Document...")
|
||||
assistant = WordMacAssistant();
|
||||
default:
|
||||
NSLog("Generic File...")
|
||||
assistantFlag = true
|
||||
}
|
||||
|
||||
let legacyUtilities: LegacyUtilities = LegacyUtilities()
|
||||
|
||||
if assistantFlag {
|
||||
NSLog("Is assistant...")
|
||||
legacyUtilities.undoMirror()
|
||||
//TODO: Window to mirror
|
||||
NSWorkspace.shared.openFile(filename)
|
||||
} else {
|
||||
if assistant.shouldUnmirror {
|
||||
legacyUtilities.undoMirror()
|
||||
}
|
||||
|
||||
NSLog("Opening file...")
|
||||
NSWorkspace.shared.openFile(filename, withApplication: assistant.applicationName)
|
||||
NSLog("Sleeping...")
|
||||
sleep(assistant!.delay)
|
||||
NSLog("Activating application...")
|
||||
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: assistant.bundleID)
|
||||
for app in apps as [NSRunningApplication] {
|
||||
if app.bundleIdentifier == assistant.bundleID {
|
||||
app.activate(options: .activateIgnoringOtherApps)
|
||||
}
|
||||
}
|
||||
|
||||
NSLog("Running Automation...")
|
||||
if self.runAutomation() {
|
||||
NSLog("Mirror check...")
|
||||
if assistant.mirrored {
|
||||
NSLog("Mirroring...")
|
||||
legacyUtilities.setupMirror()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Start recording code
|
||||
var screens = 2
|
||||
|
||||
//TODO: get actual number of screens as mirroring/unmirroring should be done at this point
|
||||
if assistant.mirrored {
|
||||
screens = 1
|
||||
}
|
||||
|
||||
let utils = Utilities()
|
||||
let rec_result = utils.startRecording(filename: filename,num_screens: screens)
|
||||
NSLog("Recording result...")
|
||||
NSLog(rec_result)
|
||||
//End recording code
|
||||
|
||||
// Return true for successfully opening the file.
|
||||
return true
|
||||
}
|
||||
|
||||
//TODO: Merge this and security as it is the same code
|
||||
func runAutomation() -> Bool {
|
||||
NSLog("Run automation...")
|
||||
NSLog(assistant.scriptString)
|
||||
let script = OSAScript.init(source: assistant.scriptString, language: OSALanguage.init(forName: "JavaScript"));
|
||||
var compileError : NSDictionary?
|
||||
script.compileAndReturnError(&compileError)
|
||||
if let compileError = compileError {
|
||||
NSLog("Compile Error...")
|
||||
print(compileError);
|
||||
return false;
|
||||
}
|
||||
var scriptError : NSDictionary?
|
||||
let result = script.executeAndReturnError(&scriptError)
|
||||
if let scriptError = scriptError {
|
||||
NSLog("Print error...")
|
||||
NSLog(scriptError.description)
|
||||
}
|
||||
else if let result = result?.stringValue {
|
||||
NSLog("Result...")
|
||||
print(result)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func triggerSecurity() -> Bool {
|
||||
NSLog("Trigger Security...")
|
||||
|
||||
ApplicationNameConstants.allCases.forEach {
|
||||
let application_name = $0.rawValue
|
||||
let application_security_script = """
|
||||
var app = Application('\(application_name)')
|
||||
delay(1)
|
||||
app.activate()
|
||||
"""
|
||||
executeSecurityScript(script: application_security_script)
|
||||
}
|
||||
|
||||
let final_security_script = """
|
||||
var finder = Application('Finder')
|
||||
finder.activate()
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(50)
|
||||
"""
|
||||
NSLog(final_security_script)
|
||||
executeSecurityScript(script: final_security_script)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func executeSecurityScript(script: String) {
|
||||
let script = OSAScript.init(source: script, language: OSALanguage.init(forName: "JavaScript"));
|
||||
var compileError : NSDictionary?
|
||||
script.compileAndReturnError(&compileError)
|
||||
if let compileError = compileError {
|
||||
NSLog("Security Script Compile Error...")
|
||||
print(compileError);
|
||||
}
|
||||
var scriptError : NSDictionary?
|
||||
let result = script.executeAndReturnError(&scriptError)
|
||||
if let scriptError = scriptError {
|
||||
NSLog("Print security script error...")
|
||||
NSLog(scriptError.description)
|
||||
}
|
||||
else if let result = result?.stringValue {
|
||||
NSLog("Trigger Security Result...")
|
||||
print(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setupDialog() -> Bool {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Are you setting up a new version of Master Key?"
|
||||
alert.informativeText = "If you answer Yes, each applicaiton (PowerPoint, Keynote, etc.) will open so that security permissions can be granted for automation for each application. If you answer no, no action will be taken and the application will exit immediately."
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: "Yes")
|
||||
alert.addButton(withTitle: "No")
|
||||
return alert.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// ApplicationAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ApplicationAssistant {
|
||||
var mirrored: Bool
|
||||
|
||||
var shouldUnmirror: Bool
|
||||
|
||||
var delay: UInt32
|
||||
|
||||
var applicationName: String
|
||||
|
||||
var applicationPath: String
|
||||
|
||||
var scriptString: String
|
||||
|
||||
var bundleID: String
|
||||
|
||||
init() {
|
||||
mirrored = false
|
||||
shouldUnmirror = true
|
||||
delay = 0
|
||||
applicationName = ""
|
||||
applicationPath = ""
|
||||
scriptString = """
|
||||
"""
|
||||
bundleID = "xxxxxxxxxxxx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AcrobatAssistant: ApplicationAssistant {
|
||||
// let mirrored = false
|
||||
// let delay = 3.0
|
||||
// let automationType = "JavaScript" //JSX or AppleScript, osascript might not care therefore remove this
|
||||
//
|
||||
// let applicationName = "Microsoft PowerPoint"
|
||||
//
|
||||
// let applicationPath = "/Applications/Microsoft PowerPoint.app"
|
||||
//
|
||||
// let scriptString =
|
||||
// """
|
||||
// delay .5\nactivate application \"Adobe Reader\"\ntell application \"System Events\"\ntell process \"Adobe Reader\"\nclick menu item \"Cascade\" of menu 1 of menu bar item \"Window\" of menu bar 1\ndelay 1\nclick menu item \"Full Screen Mode\" of menu 1 of menu bar item \"View\" of menu bar 1\nend tell\nend tell\n
|
||||
//
|
||||
// se.processes['Finder'].windows[0].toolbars[0].actions['AXShowMenu'].perform()
|
||||
//
|
||||
//
|
||||
// var se = Application('System Events')
|
||||
//
|
||||
// seApp.keystroke('c', { using: 'command down' }) // Press ⌘C
|
||||
// delay(0.2) // adjust the delay as needed
|
||||
//
|
||||
// var app = Application.currentApplication()
|
||||
// app.includeStandardAdditions = true
|
||||
//
|
||||
// var powerpoint = Application('Microsoft PowerPoint')
|
||||
// powerpoint.activate()
|
||||
// delay(1)
|
||||
// powerpoint.activate()
|
||||
// """
|
||||
|
||||
// osascript -l JavaScript -e 'Application("iTunes").currentTrack.name()'
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = true
|
||||
|
||||
shouldUnmirror = false
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "Adobe Acrobat Reader DC"
|
||||
|
||||
applicationPath = "/Applications/Adobe Acrobat Reader DC.app"
|
||||
|
||||
bundleID = "com.adobe.Reader"
|
||||
|
||||
// tell application "Adobe Acrobat Reader DC"
|
||||
// open input -- args "pagemode=FullScreen"
|
||||
// activate
|
||||
// end tell
|
||||
|
||||
// var acrobat = Application('Adobe Acrobat Reader DC')
|
||||
//
|
||||
// acrobat.activate()
|
||||
//
|
||||
// var app = Application.currentApplication()
|
||||
// app.includeStandardAdditions = true
|
||||
//
|
||||
// acrobat.activate()
|
||||
// delay(1)
|
||||
// acrobat.activate()
|
||||
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(37, { using: 'command down' })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// AcrobatWindowsAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/17/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AcrobatWindowsAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = true
|
||||
|
||||
shouldUnmirror = false
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "Acrobat Reader Windows"
|
||||
|
||||
applicationPath = "/Applications/Acrobat Reader Windows.app"
|
||||
|
||||
//ctrl + l
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(108, { using: 'control down' })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// ExcelAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/19/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class KeynoteAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = false
|
||||
shouldUnmirror = true
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "Keynote"
|
||||
|
||||
applicationPath = "/Applications/Keynote.app"
|
||||
|
||||
bundleID = "com.apple.iWork.Keynote"
|
||||
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var keynote = Application('Keynote')
|
||||
|
||||
var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
||||
keynote.activate()
|
||||
delay(1)
|
||||
keynote.activate()
|
||||
|
||||
var doc = keynote.documents[0]
|
||||
var slide = doc.slides[0]
|
||||
doc.start()
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LegacyKeynoteAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = false
|
||||
|
||||
shouldUnmirror = true
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "Keynote"
|
||||
|
||||
applicationPath = "/Applications/iWork '09/Keynote.app"
|
||||
|
||||
bundleID = "com.apple.iWork.Keynote"
|
||||
|
||||
|
||||
|
||||
// delay(1)
|
||||
// var keynote = Application('Keynote')
|
||||
//
|
||||
// var app = Application.currentApplication()
|
||||
// app.includeStandardAdditions = true
|
||||
//
|
||||
// keynote.activate()
|
||||
// delay(1)
|
||||
// keynote.activate()
|
||||
//
|
||||
// var doc = keynote.documents[0]
|
||||
// var slide = doc.slides[0]
|
||||
// doc.start()
|
||||
|
||||
//cmd + esc
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var keynote = Application('Keynote')
|
||||
|
||||
var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
||||
keynote.activate()
|
||||
delay(1)
|
||||
keynote.activate()
|
||||
|
||||
var doc = keynote.documents[0]
|
||||
var slide = doc.slides[0]
|
||||
doc.start()
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// LibreOfficeAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/7/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LibreOfficeAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = false
|
||||
shouldUnmirror = true
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "LibreOffice"
|
||||
|
||||
applicationPath = "/Applications/LibreOffice.app"
|
||||
|
||||
bundleID = "org.libreoffice.script"
|
||||
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var lo = Application('LibreOffice')
|
||||
|
||||
lo.activate()
|
||||
|
||||
var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
||||
lo.activate()
|
||||
delay(1)
|
||||
lo.activate()
|
||||
|
||||
var se = Application('System Events')
|
||||
se.keyCode(96)
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PowerPointMacAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = false
|
||||
|
||||
shouldUnmirror = true
|
||||
|
||||
delay = 3
|
||||
|
||||
applicationName = "Microsoft PowerPoint"
|
||||
|
||||
applicationPath = "/Applications/Microsoft PowerPoint.app"
|
||||
|
||||
bundleID = "com.microsoft.Powerpoint"
|
||||
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(36, { using: 'command down' })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PowerPointWindowsAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = false
|
||||
|
||||
shouldUnmirror = true
|
||||
|
||||
delay = 3
|
||||
|
||||
applicationName = "Microsoft Office PowerPoint"
|
||||
|
||||
applicationPath = "/Applications/Microsoft Office PowerPoint.app"
|
||||
|
||||
//Not overriding bundleID and keeping the superclass xxxxxxxxxxxx bundleID. Automation code handles making sure Windows PPTs are active.
|
||||
|
||||
scriptString = """
|
||||
var powerpoint = Application('Microsoft Office PowerPoint')
|
||||
|
||||
var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
||||
delay(1)
|
||||
powerpoint.activate()
|
||||
|
||||
var se = Application('System Events')
|
||||
se.keyCode(96)
|
||||
delay (2)
|
||||
powerpoint.activate()
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// PowerPointMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class VideoAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = true
|
||||
shouldUnmirror = false
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "VLC"
|
||||
|
||||
applicationPath = "/Applications/VLC.app"
|
||||
|
||||
bundleID = "org.videolan.vlc"
|
||||
|
||||
// tell application "Adobe Acrobat Reader DC"
|
||||
// open input -- args "pagemode=FullScreen"
|
||||
// activate
|
||||
// end tell
|
||||
|
||||
// var acrobat = Application('Adobe Acrobat Reader DC')
|
||||
//
|
||||
// acrobat.activate()
|
||||
//
|
||||
// var app = Application.currentApplication()
|
||||
// app.includeStandardAdditions = true
|
||||
//
|
||||
// acrobat.activate()
|
||||
// delay(1)
|
||||
// acrobat.activate()
|
||||
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(3, { using: 'command down' })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// WindowsVideoAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/17/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class WindowsVideoAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = true
|
||||
|
||||
shouldUnmirror = false
|
||||
|
||||
delay = 2
|
||||
|
||||
applicationName = "VLC Windows"
|
||||
|
||||
applicationPath = "/Applications/VLC Windows.app"
|
||||
|
||||
//alt + enter
|
||||
scriptString = """
|
||||
delay(1)
|
||||
var se = Application('System Events')
|
||||
se.keyCode(36, { using: 'option down' })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// WordMacAssistant.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/17/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class WordMacAssistant: ApplicationAssistant {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
mirrored = true
|
||||
|
||||
shouldUnmirror = false
|
||||
|
||||
delay = 3
|
||||
|
||||
applicationName = "Microsoft Word"
|
||||
|
||||
applicationPath = "/Applications/Microsoft Word.app"
|
||||
|
||||
bundleID = "com.microsoft.Word"
|
||||
|
||||
scriptString = """
|
||||
var word = Application('Microsoft Word')
|
||||
|
||||
var app = Application.currentApplication()
|
||||
app.includeStandardAdditions = true
|
||||
|
||||
delay(1)
|
||||
word.activate()
|
||||
|
||||
var se = Application('System Events')
|
||||
se.keyCode(53, { using: 'command down' })
|
||||
delay (2)
|
||||
word.activate()
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="masterkey" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="Master Key" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Master Key" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About masterkey" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide masterkey" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit masterkey" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="Master Key Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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>NSAppleEventsUsageDescription</key>
|
||||
<string></string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Any File</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.content</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>pptxmac</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>OSIT PPTX Mac</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>pptxwin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>OSIT PPTX Win</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>pptmac</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>OSIT PPT Mac</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>pptwin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>OSIT PPT Win</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.6</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 One Sky IT. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// LegacyUtilities.h
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/6/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#ifndef LegacyUtilities_h
|
||||
#define LegacyUtilities_h
|
||||
|
||||
@interface LegacyUtilities: NSObject
|
||||
|
||||
-(void) setupMirror;
|
||||
-(void) doMirror;
|
||||
|
||||
-(void) undoMirror;
|
||||
-(void) executeMirrorAction;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* LegacyUtilities_h */
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// LegacyUtilities.m
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/6/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
#import "LegacyUtilities.h"
|
||||
|
||||
@implementation LegacyUtilities : NSObject
|
||||
|
||||
-(void) setupMirror {
|
||||
CGDisplayCount numberOfActiveDspys;
|
||||
CGDisplayCount numberOfOnlineDspys;
|
||||
|
||||
CGDisplayCount numberOfTotalDspys = 2; // The number of total displays
|
||||
|
||||
CGDirectDisplayID activeDspys[] = {0,0};
|
||||
CGDirectDisplayID onlineDspys[] = {0,0};
|
||||
CGDirectDisplayID secondaryDspy;
|
||||
|
||||
CGDisplayErr activeError = CGGetActiveDisplayList (numberOfTotalDspys,activeDspys,&numberOfActiveDspys);
|
||||
|
||||
if (activeError!=0) NSLog(@"Error in obtaining active diplay list: %d\n",activeError);
|
||||
|
||||
CGDisplayErr onlineError = CGGetOnlineDisplayList (numberOfTotalDspys,onlineDspys,&numberOfOnlineDspys);
|
||||
|
||||
if (onlineError!=0) NSLog(@"Error in obtaining online diplay list: %d\n",onlineError);
|
||||
|
||||
CGDisplayConfigRef configRef;
|
||||
CGError err = CGBeginDisplayConfiguration (&configRef);
|
||||
if (err != 0) NSLog(@"Error with CGBeginDisplayConfiguration: %d\n",err);
|
||||
|
||||
if (numberOfOnlineDspys==2) {
|
||||
|
||||
if (onlineDspys[0]==CGMainDisplayID()){
|
||||
secondaryDspy = onlineDspys[1];
|
||||
} else {
|
||||
secondaryDspy = onlineDspys[0];
|
||||
}
|
||||
|
||||
CGDisplayConfigRef configRef;
|
||||
CGError err = CGBeginDisplayConfiguration (&configRef);
|
||||
if (err != 0) NSLog(@"Error with CGBeginDisplayConfiguration: %d\n",err);
|
||||
|
||||
if (numberOfActiveDspys==2) { // Displays are unmirrored -> mirror them
|
||||
err = CGConfigureDisplayMirrorOfDisplay (configRef,secondaryDspy,CGMainDisplayID());
|
||||
NSLog(@"ConfigureMirror Error: %d",err);
|
||||
}
|
||||
err = CGCompleteDisplayConfiguration (configRef,kCGConfigurePermanently);
|
||||
NSLog(@"Mirror CompleteConfig Error: %d",err);
|
||||
|
||||
} else {
|
||||
if (numberOfOnlineDspys>2) {
|
||||
printf("Cannot handle more than 2 displays at this time. %d displays detected.\n",numberOfOnlineDspys);
|
||||
} else {
|
||||
printf("No secondary display detected.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) doMirror {
|
||||
NSEvent *thisEvent = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:CGPointMake(0,0) modifierFlags:0xa00 timestamp:0 windowNumber:0 context:0 subtype:8 data1:(NX_KEYTYPE_VIDMIRROR<<16|(0xa <<8)) data2:-1];
|
||||
CGEventRef cThisEvent = [thisEvent CGEvent];
|
||||
CGEventPost(0, (cThisEvent));
|
||||
|
||||
NSEvent *thatEvent = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:NSMakePoint(0,0) modifierFlags:0xb00 timestamp:0 windowNumber:0 context:[NSGraphicsContext currentContext] subtype:8 data1:(NX_KEYTYPE_VIDMIRROR<<16|(0xb <<8)) data2:-1];
|
||||
CGEventRef cThatEvent = [thatEvent CGEvent];
|
||||
CGEventPost(0, (cThatEvent));
|
||||
}
|
||||
|
||||
-(void) undoMirror {
|
||||
enum MirrorMode {
|
||||
off,
|
||||
} mode;
|
||||
|
||||
mode = off;
|
||||
|
||||
CGDisplayCount numberOfActiveDspys;
|
||||
CGDisplayCount numberOfOnlineDspys;
|
||||
|
||||
CGDisplayCount numberOfTotalDspys = 2;
|
||||
|
||||
CGDirectDisplayID activeDspys[] = {0,0};
|
||||
CGDirectDisplayID onlineDspys[] = {0,0};
|
||||
CGDirectDisplayID secondaryDspy;
|
||||
|
||||
CGDisplayErr activeError = CGGetActiveDisplayList (numberOfTotalDspys,activeDspys,&numberOfActiveDspys);
|
||||
|
||||
if (activeError!=0) NSLog(@"Error in obtaining active diplay list: %d\n",activeError);
|
||||
|
||||
CGDisplayErr onlineError = CGGetOnlineDisplayList (numberOfTotalDspys,onlineDspys,&numberOfOnlineDspys);
|
||||
|
||||
if (onlineError!=0) NSLog(@"Error in obtaining online diplay list: %d\n",onlineError);
|
||||
|
||||
if (numberOfOnlineDspys==2) { // Online displays = physical displays regardless of mirror status
|
||||
|
||||
if (onlineDspys[0]==CGMainDisplayID()){
|
||||
secondaryDspy = onlineDspys[1];
|
||||
} else {
|
||||
secondaryDspy = onlineDspys[0];
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case off:
|
||||
if (numberOfActiveDspys!=2) // Active displays = software displays (mirror = 1)
|
||||
[self executeMirrorAction];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (numberOfOnlineDspys>2) {
|
||||
printf("Cannot handle more than 2 displays at this time. %d displays detected.\n",numberOfOnlineDspys);
|
||||
} else {
|
||||
printf("No secondary display detected.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) executeMirrorAction {
|
||||
NSEvent *thisEvent = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:CGPointMake(0,0) modifierFlags:0xa00 timestamp:0 windowNumber:0 context:0 subtype:8 data1:(NX_KEYTYPE_VIDMIRROR<<16|(0xa <<8)) data2:-1];
|
||||
CGEventRef cThisEvent = [thisEvent CGEvent];
|
||||
CGEventPost(0, (cThisEvent));
|
||||
|
||||
NSEvent *thatEvent = [NSEvent otherEventWithType:NSEventTypeSystemDefined location:NSMakePoint(0,0) modifierFlags:0xb00 timestamp:0 windowNumber:0 context:[NSGraphicsContext currentContext] subtype:8 data1:(NX_KEYTYPE_VIDMIRROR<<16|(0xb <<8)) data2:-1];
|
||||
CGEventRef cThatEvent = [thatEvent CGEvent];
|
||||
CGEventPost(0, (cThatEvent));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Utilities.swift
|
||||
// masterkey
|
||||
//
|
||||
// Created by Ian Kohl on 4/6/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Utilities {
|
||||
func startRecording(filename: String, num_screens: Int) -> String
|
||||
{
|
||||
//Create output filename
|
||||
let timestamp = String(NSDate().timeIntervalSince1970)
|
||||
let home = NSHomeDirectory()
|
||||
let recording_filename = timestamp + filename + ".mkv"
|
||||
let recording_path = "\(home)/recordings/\(recording_filename)"
|
||||
|
||||
let device_string = "\"\(num_screens):0\""
|
||||
|
||||
//Get Devices
|
||||
//ffmpeg -f avfoundation -list_devices true -i ""
|
||||
|
||||
//Start recording
|
||||
//ffmpeg -f avfoundation -i "2:<audio device index>" ~/recordings/filename.mkv
|
||||
|
||||
let task:Process = Process()
|
||||
let pipe:Pipe = Pipe()
|
||||
|
||||
task.launchPath = "/usr/local/bin/ffmpeg"
|
||||
task.arguments = ["-f","avfoundation","-i",device_string,recording_path]
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let handle = pipe.fileHandleForReading
|
||||
let data = handle.readDataToEndOfFile()
|
||||
let result_s = String(data: data, encoding: String.Encoding.utf8)!
|
||||
return result_s
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Microsoft Office PowerPoint
|
||||
VLC Windows
|
||||
Acrobat Reader Windows
|
||||
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "LegacyUtilities.h"
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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/>
|
||||
</plist>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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>NSAppleEventsUsageDescription</key>
|
||||
<string></string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// masterkeyTests.swift
|
||||
// masterkeyTests
|
||||
//
|
||||
// Created by Ian Kohl on 2/28/19.
|
||||
// Copyright © 2019 One Sky IT. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import masterkey
|
||||
|
||||
class masterkeyTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
2159
package-lock.json
generated
2159
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -4,18 +4,20 @@
|
||||
"description": "AE Native Launcher V3",
|
||||
"main": "dist/main/index.js",
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/patch-packager-unzip.js",
|
||||
"start": "tsc && electron .",
|
||||
"dev": "tsc && electron .",
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"package:linux": "tsc && electron-packager . aether_launcher --platform=linux --arch=x64 --out=builds --overwrite --prune=true",
|
||||
"package:mac": "tsc && electron-packager . aether_launcher --platform=darwin --arch=x64,arm64 --out=builds --overwrite --prune=true --icon=resources/img/osit_logo.icns"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.7",
|
||||
"electron": "^34.0.0",
|
||||
"electron-packager": "^17.1.2",
|
||||
"@types/node": "^22.19.0",
|
||||
"electron": "^42.0.1",
|
||||
"@electron/packager": "^20.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.3"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2"
|
||||
|
||||
BIN
resources/img/favicon.ico
Executable file
BIN
resources/img/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/img/oneskyit_logo.png
Normal file
BIN
resources/img/oneskyit_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
resources/img/osit_logo.icns
Normal file
BIN
resources/img/osit_logo.icns
Normal file
Binary file not shown.
BIN
resources/img/osit_logo_150.png
Normal file
BIN
resources/img/osit_logo_150.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
23
scripts/patch-packager-unzip.js
Normal file
23
scripts/patch-packager-unzip.js
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env node
|
||||
// extract-zip/yauzl streams hang on Node 26 (no data events emitted).
|
||||
// bsdtar (libarchive) handles both Linux and macOS zips including chained symlinks in .app bundles.
|
||||
const { writeFileSync, existsSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
|
||||
const target = resolve('node_modules/@electron/packager/dist/unzip.js');
|
||||
if (!existsSync(target)) {
|
||||
console.log('patch-packager-unzip: target not found, skipping');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const patched = `import { execSync } from 'node:child_process';
|
||||
// extract-zip/yauzl streams are broken on Node 26; use bsdtar (libarchive) instead.
|
||||
// bsdtar correctly handles chained symlinks in macOS .app bundles that 7z refuses.
|
||||
export async function extractElectronZip(zipPath, targetDir) {
|
||||
execSync(\`bsdtar -xf "\${zipPath}" -C "\${targetDir}"\`, { stdio: 'pipe' });
|
||||
}
|
||||
//# sourceMappingURL=unzip.js.map
|
||||
`;
|
||||
|
||||
writeFileSync(target, patched);
|
||||
console.log('patch-packager-unzip: patched @electron/packager/dist/unzip.js to use bsdtar');
|
||||
@@ -12,15 +12,15 @@ export async function fetchFullConfig(seed: SeedConfig): Promise<any> {
|
||||
for (const baseUrl of apiUrls) {
|
||||
try {
|
||||
console.log(`Bootstrap: Attempting connection to ${baseUrl}...`);
|
||||
|
||||
|
||||
// --- STEP 1: Get Device Config ---
|
||||
const deviceUrl = `${baseUrl}/v3/crud/event_device/${seed.event_device_id}`;
|
||||
const deviceResponse = await fetch(deviceUrl, {
|
||||
method: 'GET',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
'x-no-account-id': 'bypass'
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,25 +30,21 @@ export async function fetchFullConfig(seed: SeedConfig): Promise<any> {
|
||||
|
||||
const deviceResult = await deviceResponse.json();
|
||||
const deviceData = deviceResult.data || deviceResult;
|
||||
|
||||
|
||||
// Use 'app_base_url' as the FQDN for the site lookup
|
||||
const fqdn = deviceData.app_base_url || 'native-demo.oneskyit.com';
|
||||
|
||||
// --- STEP 2: Get Site Context ---
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search`;
|
||||
const searchUrl = `${baseUrl}/v3/crud/site_domain/search?limit=1`;
|
||||
const siteResponse = await fetch(searchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': seed.aether_api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'x-account-id': deviceData.account_id_random || deviceData.account_id || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
search_query: {
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
},
|
||||
limit: 1
|
||||
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function registerFileHandlers() {
|
||||
|
||||
ipcMain.handle('native:check-cache', async (event, { cache_root, hash, hash_prefix_length = 2, verify_hash = false }) => {
|
||||
const full_path = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
|
||||
|
||||
if (!fs.existsSync(full_path)) return false;
|
||||
|
||||
if (verify_hash) {
|
||||
@@ -42,7 +42,7 @@ export function registerFileHandlers() {
|
||||
const tmp_path = `${full_path}.tmp`;
|
||||
|
||||
if (endpoints_in_progress.includes(url)) return { success: true, status: 'in_progress' };
|
||||
|
||||
|
||||
// 1. If final file exists, skip
|
||||
if (fs.existsSync(full_path)) return { success: true, path: full_path, status: 'exists' };
|
||||
|
||||
@@ -67,8 +67,7 @@ export function registerFileHandlers() {
|
||||
method: 'get', url, responseType: 'stream',
|
||||
headers: {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id || '',
|
||||
'x-no-account-id': 'Nothing to See Here'
|
||||
'x-account-id': account_id || ''
|
||||
}
|
||||
});
|
||||
|
||||
@@ -101,24 +100,62 @@ export function registerFileHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2 }) => {
|
||||
ipcMain.handle('native:launch-from-cache', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2, script_template = null }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
const expanded_temp = expandPath(temp_root);
|
||||
const target = path.join(expanded_temp, filename);
|
||||
|
||||
|
||||
console.log(`Native: Launching from Cache -> ${filename}`);
|
||||
|
||||
if (!fs.existsSync(source)) {
|
||||
return { success: false, error: `File not in cache: ${hash}` };
|
||||
}
|
||||
|
||||
if (!fs.existsSync(expanded_temp)) fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
|
||||
|
||||
// 1. Copy the file to temp folder with original name
|
||||
fs.copyFileSync(source, target);
|
||||
|
||||
// 2. Determine file type
|
||||
|
||||
// 2a. Data-driven script override (no rebuild needed for script changes).
|
||||
// Set via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
// Format: AppleScript string with {{path}} placeholder, OR "shell:<cmd> {{path}}"
|
||||
if (script_template) {
|
||||
const resolved = (script_template as string).replace(/\{\{path\}\}/g, target);
|
||||
if (resolved.startsWith('shell:')) {
|
||||
const cmd = resolved.slice(6).trim();
|
||||
console.log(`Native: Running custom shell script for ${filename}`);
|
||||
return new Promise((resolve_fn) => {
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) resolve_fn({ success: false, error: err.message, stderr: stderr.trim() });
|
||||
else resolve_fn({ success: true, stdout: stdout.trim() });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Treat as AppleScript — write to temp .scpt file (same hardened approach used below)
|
||||
console.log(`Native: Running custom AppleScript for ${filename}`);
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve_fn) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, resolved.trim());
|
||||
} catch (e: any) {
|
||||
resolve_fn({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
if (err) resolve_fn({ success: false, error: err.message });
|
||||
else resolve_fn({ success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2b. Determine file type (legacy hardcoded launch logic — used when no script_template provided)
|
||||
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
||||
const is_pres = ['pptx', 'ppt', 'key', 'pdf', 'odp'].includes(ext);
|
||||
|
||||
// 3. Optimized Launch (LibreOffice / AppleScript)
|
||||
// 3. Hardcoded launch (legacy — still the default when no script_template is configured)
|
||||
if (is_pres) {
|
||||
if (os.platform() === 'linux') {
|
||||
console.log(`Native: Launching LibreOffice (--impress) for ${target}`);
|
||||
@@ -146,8 +183,10 @@ export function registerFileHandlers() {
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${target}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
delay 3
|
||||
end tell
|
||||
tell application "System Events"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
`;
|
||||
}
|
||||
@@ -181,4 +220,33 @@ export function registerFileHandlers() {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Thin primitive: copy a cached file to the temp directory with its original filename,
|
||||
// then return the resolved path. The caller (Svelte side) decides what to do next —
|
||||
// run_osascript, run_cmd, open_local_file, etc.
|
||||
//
|
||||
// This is the preferred building block for custom launch flows. Use launch_from_cache
|
||||
// when the built-in hardcoded logic is sufficient; use copy_from_cache_to_temp when
|
||||
// you want full control over what happens after the file lands in temp.
|
||||
ipcMain.handle('native:copy-from-cache-to-temp', async (event, { cache_root, hash, temp_root, filename, hash_prefix_length = 2 }) => {
|
||||
try {
|
||||
const source = get_organized_hashed_path(cache_root, hash, hash_prefix_length);
|
||||
|
||||
if (!fs.existsSync(source)) {
|
||||
return { success: false, error: `File not in cache: ${hash}` };
|
||||
}
|
||||
|
||||
const expanded_temp = expandPath(temp_root);
|
||||
const target = path.join(expanded_temp, filename);
|
||||
|
||||
if (!fs.existsSync(expanded_temp)) fs.mkdirSync(expanded_temp, { recursive: true });
|
||||
|
||||
fs.copyFileSync(source, target);
|
||||
console.log(`Native: Copied from cache to temp -> ${target}`);
|
||||
|
||||
return { success: true, path: target };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import { exec, execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { expandPath } from './file_utils';
|
||||
|
||||
@@ -32,10 +33,27 @@ export function registerShellHandlers() {
|
||||
|
||||
ipcMain.handle('native:run-osascript', async (event, script: string) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'AppleScript is only available on macOS' };
|
||||
const escapedScript = script.replace(/"/g, '\"');
|
||||
const cmd = `osascript -e "${escapedScript}"`;
|
||||
|
||||
// HARDENED: Write script to a temp .scpt file rather than passing inline via -e.
|
||||
// The old -e approach (`osascript -e "..."`) has two fatal flaws:
|
||||
// 1. It breaks on multi-line scripts.
|
||||
// 2. It breaks on paths containing spaces or special characters (quotes, parens, etc.)
|
||||
// Writing to a file sidesteps both — no shell escaping needed at all.
|
||||
// The .scpt file is deleted immediately after execution (success or failure).
|
||||
// Worst case on crash: a stale .scpt in /tmp, cleared on next OS reboot.
|
||||
//
|
||||
// LEGACY (removed): const cmd = `osascript -e "${script.replace(/"/g, '\\"')}"`;
|
||||
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_osa_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script.trim());
|
||||
} catch (e: any) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (error, stdout, stderr) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null });
|
||||
});
|
||||
});
|
||||
@@ -45,8 +63,8 @@ export function registerShellHandlers() {
|
||||
console.log(`Native: Killing processes -> `, process_name_li);
|
||||
const results = [];
|
||||
for (const name of process_name_li) {
|
||||
const cmd = os.platform() === 'win32'
|
||||
? `taskkill /F /IM ${name} /T`
|
||||
const cmd = os.platform() === 'win32'
|
||||
? `taskkill /F /IM ${name} /T`
|
||||
: `pkill -f ${name}`;
|
||||
try {
|
||||
execSync(cmd);
|
||||
@@ -82,28 +100,35 @@ export function registerShellHandlers() {
|
||||
let script = '';
|
||||
if (appType === 'keynote') {
|
||||
script = `
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`;
|
||||
tell application "Keynote"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
start (front document)
|
||||
end tell
|
||||
`.trim();
|
||||
} else if (appType === 'powerpoint') {
|
||||
script = `
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`;
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
open (POSIX file "${cleanedPath}")
|
||||
delay 1
|
||||
run slide show of active presentation
|
||||
end tell
|
||||
`.trim();
|
||||
}
|
||||
|
||||
if (script) {
|
||||
const tmp_script_path = path.join(os.tmpdir(), `ae_launch_${Date.now()}.scpt`);
|
||||
return new Promise((resolve) => {
|
||||
const escapedScript = script.replace(/"/g, '\\"');
|
||||
exec(`osascript -e "${escapedScript}"`, (err, stdout, stderr) => {
|
||||
try {
|
||||
fs.writeFileSync(tmp_script_path, script);
|
||||
} catch (e: any) {
|
||||
resolve({ success: false, error: `Failed to write AppleScript temp file: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
exec(`osascript "${tmp_script_path}"`, (err) => {
|
||||
try { fs.unlinkSync(tmp_script_path); } catch {}
|
||||
if (err) resolve({ success: false, error: err.message });
|
||||
else resolve({ success: true });
|
||||
});
|
||||
@@ -117,7 +142,7 @@ export function registerShellHandlers() {
|
||||
|
||||
ipcMain.handle('native:control-presentation', async (event, { app, action }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Presentation control is only available on macOS' };
|
||||
|
||||
|
||||
let script = '';
|
||||
if (app === 'powerpoint') {
|
||||
switch (action) {
|
||||
|
||||
@@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld('aetherNative', {
|
||||
|
||||
check_cache: (args: any) => ipcRenderer.invoke('native:check-cache', args),
|
||||
download_to_cache: (args: any) => ipcRenderer.invoke('native:download-to-cache', args),
|
||||
copy_from_cache_to_temp: (args: any) => ipcRenderer.invoke('native:copy-from-cache-to-temp', args),
|
||||
launch_from_cache: (args: any) => ipcRenderer.invoke('native:launch-from-cache', args),
|
||||
launch_presentation: (args: any) => ipcRenderer.invoke('native:launch-presentation', args),
|
||||
control_presentation: (args: any) => ipcRenderer.invoke('native:control-presentation', args),
|
||||
|
||||
@@ -1,55 +1,90 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_device_lookup():
|
||||
device_id = 'dbgMWS3KEHE'
|
||||
api_key = 'INSdG85ANwsEIru3nUttMw'
|
||||
base_url = 'https://dev-api.oneskyit.com'
|
||||
endpoint = f'{base_url}/v3/crud/event_device/{device_id}'
|
||||
|
||||
headers = {
|
||||
def test_bootstrap(device_id, api_key, base_url='https://dev-api.oneskyit.com'):
|
||||
"""
|
||||
Replicates the two-step bootstrap sequence in api_client.ts:
|
||||
Step 1: GET event_device → extract account_id + app_base_url (fqdn)
|
||||
Step 2: POST site_domain/search → returns the correct site context
|
||||
"""
|
||||
print(f'\n=== Bootstrap test ===')
|
||||
print(f'Device: {device_id}')
|
||||
print(f'Base URL: {base_url}')
|
||||
|
||||
# --- Step 1: Get Device Config ---
|
||||
device_url = f'{base_url}/v3/crud/event_device/{device_id}'
|
||||
headers_step1 = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': api_key,
|
||||
'x-no-account-id': 'Nothing to See Here',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
params = {
|
||||
'view': 'enriched'
|
||||
'x-no-account-id': 'bypass',
|
||||
}
|
||||
|
||||
print(f'Testing lookup for device: {device_id}')
|
||||
print(f'Endpoint: {endpoint}')
|
||||
|
||||
print(f'\n-- Step 1: GET {device_url}')
|
||||
try:
|
||||
response = requests.get(endpoint, headers=headers, params=params)
|
||||
print(f'Status Code: {response.status_code}')
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
device_data = data.get('data', {})
|
||||
|
||||
print('Returned Fields (Key Values):')
|
||||
important_fields = [
|
||||
'account_id_random',
|
||||
'app_base_url',
|
||||
'code',
|
||||
'name',
|
||||
'event_id_random',
|
||||
'event_location_id_random',
|
||||
'local_file_cache_path',
|
||||
'host_file_temp_path',
|
||||
'recording_path',
|
||||
'cfg_json'
|
||||
]
|
||||
|
||||
for field in important_fields:
|
||||
val = device_data.get(field, 'MISSING')
|
||||
print(f' {field}: {val}')
|
||||
else:
|
||||
print(f'Error Response: {response.text}')
|
||||
|
||||
r1 = requests.get(device_url, headers=headers_step1)
|
||||
print(f' Status: {r1.status_code}')
|
||||
if r1.status_code != 200:
|
||||
print(f' Error: {r1.text}')
|
||||
return
|
||||
|
||||
device_data = r1.json().get('data', {})
|
||||
important_fields = [
|
||||
'account_id', 'app_base_url', 'code', 'name',
|
||||
'event_id', 'event_location_id',
|
||||
'local_file_cache_path', 'host_file_temp_path', 'recording_path',
|
||||
]
|
||||
for field in important_fields:
|
||||
print(f' {field}: {device_data.get(field, "MISSING")}')
|
||||
|
||||
account_id = device_data.get('account_id') or device_data.get('account_id_random')
|
||||
fqdn = device_data.get('app_base_url', 'native-demo.oneskyit.com')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Request failed: {e}')
|
||||
print(f' Request failed: {e}')
|
||||
return
|
||||
|
||||
# --- Step 2: Get Site Context ---
|
||||
search_url = f'{base_url}/v3/crud/site_domain/search?limit=1'
|
||||
headers_step2 = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id or '',
|
||||
}
|
||||
body = {
|
||||
'and': [{'field': 'fqdn', 'op': 'eq', 'value': fqdn}]
|
||||
}
|
||||
|
||||
print(f'\n-- Step 2: POST {search_url}')
|
||||
print(f' Searching for fqdn: {fqdn}')
|
||||
try:
|
||||
r2 = requests.post(search_url, headers=headers_step2, json=body)
|
||||
print(f' Status: {r2.status_code}')
|
||||
if r2.status_code != 200:
|
||||
print(f' Error: {r2.text}')
|
||||
return
|
||||
|
||||
results = r2.json().get('data', [])
|
||||
print(f' Results returned: {len(results)}')
|
||||
if results:
|
||||
sd = results[0]
|
||||
print(f' fqdn: {sd.get("fqdn")}')
|
||||
print(f' account_id: {sd.get("account_id")}')
|
||||
print(f' site_id: {sd.get("site_id")}')
|
||||
if sd.get('account_id') != account_id:
|
||||
print(f' WARNING: site_domain account_id does not match device account_id!')
|
||||
else:
|
||||
print(f' OK: account_id matches device.')
|
||||
else:
|
||||
print(f' WARNING: No site_domain found for fqdn "{fqdn}"')
|
||||
|
||||
except Exception as e:
|
||||
print(f' Request failed: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_device_lookup()
|
||||
# Dev device (dev-api)
|
||||
test_bootstrap(
|
||||
device_id='dbgMWS3KEHE',
|
||||
api_key='INSdG85ANwsEIru3nUttMw',
|
||||
base_url='https://dev-api.oneskyit.com',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user