diff --git a/.gitignore b/.gitignore index d236e86..f2b5d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ pnpm-debug.log* lerna-debug.log* node_modules +build +binaries +*spec dist dist-ssr *.local diff --git a/README.md b/README.md index 19c3678..e871c3b 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,45 @@ Run the application. bun tauri dev ``` -## Running the Map Server +## Setting up FPV camera server -Ensure Docker is running then install the docker container. +Install Flask and opencv-python dependencies. + +```bash +pip install flask +``` + +```bash +pip install opencv-python +``` + + + +Install pyinstaller, this will be used to compile the `.py` file into a binary. + +```bash +pip install pyinstaller +``` + +Create binary from opencv.py + +```bash +pyinstaller --onefile .\src-tauri\opencv.py --distpath .\src-tauri\binaries\ +``` + +Ensure the`opencv` binary is in the `dist` folder. If needed move this into `binaries` folder (if there is already a file, replace it). + +Run a Node.js script to rename the binary file, as you must add your architecture to the file name. + +```bash +bun run target:triple +``` + +Now the FPV camera server should run with `bun tauri dev`. It takes time for it to spin up and once it does, make sure you refresh the camera window. + +## Setting up map server + +Ensure Docker is running then install the docker container. Note only update ```bash bun run osm:setup @@ -39,3 +75,5 @@ Run the Map Server container. ```bash bun run osm:run ``` + + diff --git a/package-lock.json b/package-lock.json index d1c739c..ceaff2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,9 @@ "version": "0.0.0", "dependencies": { "@rushstack/eslint-patch": "^1.10.3", - "@tauri-apps/api": "^1.4.0", - "@vueuse/core": "^12.7.0", + "@tauri-apps/api": "2.0.0", + "@tauri-apps/plugin-shell": "^2.2.1", + "@vueuse/core": "^12.8.2", "axios": "^1.6.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -22,15 +23,17 @@ "reka-ui": "^2.0.2", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "taurpc": "^1.8.0", "vue": "^3.2.45", "vue-flight-indicators": "^0.1.1", "vue-router": "^4.3.0", - "ws": "^8.16.0" + "ws": "^8.16.0", + "zustand": "^5.0.3" }, "devDependencies": { "@iconify-json/radix-icons": "^1.1.14", "@iconify/vue": "^4.1.2", - "@tauri-apps/cli": "^1.6.3", + "@tauri-apps/cli": "2.0.0", "@types/leaflet": "^1.9.12", "@types/node": "^18.19.34", "@vitejs/plugin-vue": "^4.0.0", @@ -902,27 +905,19 @@ } }, "node_modules/@tauri-apps/api": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.5.3.tgz", - "integrity": "sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA==", - "engines": { - "node": ">= 14.6.0", - "npm": ">= 6.6.0", - "yarn": ">= 1.19.1" - }, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0.tgz", + "integrity": "sha512-moKgCp2EX7X5GiOx/G/bmoEpkFQVVmyS98UaJU4xUVzan+E1BdwlAKcbip+cGldshYOqL4JSwAEN1OkRXeug0Q==", "funding": { "type": "opencollective", "url": "https://opencollective.com/tauri" } }, "node_modules/@tauri-apps/cli": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.3.tgz", - "integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0.tgz", + "integrity": "sha512-xxmPllRa6w/LRRcPczST3yHrYoi8l6ZZmzwabEmM0cgDdhVDmX+Y4oDJkiKD+8cVdxwwEzIuIKuaCwsX8iNsgA==", "dev": true, - "dependencies": { - "semver": ">=7.5.2" - }, "bin": { "tauri": "tauri.js" }, @@ -934,22 +929,22 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "1.6.3", - "@tauri-apps/cli-darwin-x64": "1.6.3", - "@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3", - "@tauri-apps/cli-linux-arm64-gnu": "1.6.3", - "@tauri-apps/cli-linux-arm64-musl": "1.6.3", - "@tauri-apps/cli-linux-x64-gnu": "1.6.3", - "@tauri-apps/cli-linux-x64-musl": "1.6.3", - "@tauri-apps/cli-win32-arm64-msvc": "1.6.3", - "@tauri-apps/cli-win32-ia32-msvc": "1.6.3", - "@tauri-apps/cli-win32-x64-msvc": "1.6.3" + "@tauri-apps/cli-darwin-arm64": "2.0.0", + "@tauri-apps/cli-darwin-x64": "2.0.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.0.0", + "@tauri-apps/cli-linux-arm64-musl": "2.0.0", + "@tauri-apps/cli-linux-x64-gnu": "2.0.0", + "@tauri-apps/cli-linux-x64-musl": "2.0.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.0.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.0.0", + "@tauri-apps/cli-win32-x64-msvc": "2.0.0" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz", - "integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0.tgz", + "integrity": "sha512-+agYqg2c77imaMfKw7mzqecVIDGcwr6bZMdglJ808O2UjTFzMwnAam1sU26YBYU+IyIjwOu00fm9Azpal+N/Ew==", "cpu": [ "arm64" ], @@ -963,9 +958,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz", - "integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0.tgz", + "integrity": "sha512-keN2PLTTcZmbWwFMup/NGcshmvyLnhRPChO8lbm9C5a0IY7zUNQUD7/o/zIulQdLJqDxkdpWJ1j2jTycAtvtKQ==", "cpu": [ "x64" ], @@ -979,9 +974,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz", - "integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0.tgz", + "integrity": "sha512-FQJNrlCUBb9E7Fhp5ARy+Or8lSvorG41aVrfi0cGNvv1QlIGSj77TN7SKK+L1jAGzKj1Bl2kCZIESF6Zi8N/+Q==", "cpu": [ "arm" ], @@ -995,9 +990,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz", - "integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0.tgz", + "integrity": "sha512-TK3VrZG5LK1NGueKwnZA1/3gj/qkwry001MNCHXjT6394dwrDv+digCc9Qc569h+xeH/FF71jyoiRIu3gRE6iA==", "cpu": [ "arm64" ], @@ -1011,9 +1006,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz", - "integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0.tgz", + "integrity": "sha512-E3hRmS/0m8YUYMTKZtBExpk/284CTi2nymks0dK0L1j+3KjffL7DiilnIfNFmTvWBgMrs0cVCtoaN/ba/A9mNA==", "cpu": [ "arm64" ], @@ -1027,9 +1022,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz", - "integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0.tgz", + "integrity": "sha512-veX4BJp5xnW8KmxVjchWt4oZEIvKGhuSR7qU1WpqTR21e/eTe/ksGsdXPsqOKQvv/w1X6jhqmlPvhnFmDwUJ/w==", "cpu": [ "x64" ], @@ -1043,9 +1038,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz", - "integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0.tgz", + "integrity": "sha512-9Eso/8wbsWbOyd9PZEIzN/48ZQJrUGQqGZtglcjUku0lO76mnX0fOnit4nQ57Oj0wezJPhv4mgSseG1OsTIVzw==", "cpu": [ "x64" ], @@ -1059,9 +1054,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz", - "integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0.tgz", + "integrity": "sha512-ky8vWAuDUf8WGt9+a0G/EbU0OhdIkogelh9qjIYGHbyEYAJqXfN5P40aHUEg3y8ngQ0YGwRX5ePsQsSZiiR5PQ==", "cpu": [ "arm64" ], @@ -1075,9 +1070,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz", - "integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0.tgz", + "integrity": "sha512-uD45cLZ/EBaT8o4a27tHW7t5UKFplnvDLt/uSUaCpJ3NyOTV6nMXOUrJBe+hH9hSBohqNAF7LEyYo1p932DWFg==", "cpu": [ "ia32" ], @@ -1091,9 +1086,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz", - "integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0.tgz", + "integrity": "sha512-oFlo14YMsvyhJHmmHgRuOpJ1L9w15193c1Nfj1DksS2LHj6tLzirI7YrAF9inY/XjHFjNHzYPmBpABibkf/9wQ==", "cpu": [ "x64" ], @@ -1106,16 +1101,12 @@ "node": ">= 10" } }, - "node_modules/@tauri-apps/cli/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.2.1.tgz", + "integrity": "sha512-G1GFYyWe/KlCsymuLiNImUgC8zGY0tI0Y3p8JgBCWduR5IEXlIJS+JuG1qtveitwYXlfJrsExt3enhv5l2/yhA==", + "dependencies": { + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@types/eslint": { @@ -5294,6 +5285,23 @@ "node": ">=6" } }, + "node_modules/taurpc": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/taurpc/-/taurpc-1.8.1.tgz", + "integrity": "sha512-qfR1ekhXApjbnWAqyE5qpDa/oMmKBwjNNDuqjYn7sO+ChI5qwQ4J/fS+00dVLE3I0lbZDizyY8ObSFBz3MRerQ==", + "dependencies": { + "@tauri-apps/api": "^2.0.2" + } + }, + "node_modules/taurpc/node_modules/@tauri-apps/api": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz", + "integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/terser": { "version": "5.27.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", @@ -5825,6 +5833,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz", + "integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 735fbe7..3900f35 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "osm:run": "docker-compose up osm-server", "tauri": "tauri", "format": "prettier --write src/", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "target:triple": "node scripts/target-triple.js" }, "dependencies": { "@rushstack/eslint-patch": "^1.10.3", "@tauri-apps/api": "2.0.0", - "@tauri-apps/plugin-shell": "~2", + "@tauri-apps/plugin-shell": "^2.2.1", "@vueuse/core": "^12.8.2", "axios": "^1.6.8", "class-variance-authority": "^0.7.1", diff --git a/scripts/target-triple.js b/scripts/target-triple.js new file mode 100644 index 0000000..4b4edae --- /dev/null +++ b/scripts/target-triple.js @@ -0,0 +1,16 @@ +// Node.js script to append the target triple to a binary + +import { execSync } from 'child_process'; +import fs from 'fs'; + +const extension = process.platform === 'win32' ? '.exe' : ''; + +const rustInfo = execSync('rustc -vV'); +const targetTriple = /host: (\S+)/g.exec(rustInfo)[1]; +if (!targetTriple) { + console.error('Failed to determine platform target triple'); +} +fs.renameSync( + `src-tauri/binaries/opencv${extension}`, + `src-tauri/binaries/opencv-${targetTriple}${extension}` +); \ No newline at end of file diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore index b21bd68..31134e7 100644 --- a/src-tauri/.gitignore +++ b/src-tauri/.gitignore @@ -4,4 +4,4 @@ # Generated by Tauri # will have schema files for capabilities auto-completion -/gen/schemas +/gen/schemas \ No newline at end of file diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 5721b6a..8d4fdff 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,7 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capabilities", - "windows": ["*"], + "windows": [ + "*" + ], "permissions": [ "core:path:default", "core:event:default", @@ -12,6 +14,16 @@ "core:resources:default", "core:menu:default", "core:tray:default", - "shell:allow-open" + "shell:allow-open", + "shell:default", + { + "identifier": "shell:allow-spawn", + "allow": [ + { + "name": "binaries/opencv", + "sidecar": true + } + ] + } ] -} +} \ No newline at end of file diff --git a/src-tauri/opencv.py b/src-tauri/opencv.py new file mode 100644 index 0000000..210bd1c --- /dev/null +++ b/src-tauri/opencv.py @@ -0,0 +1,50 @@ +# With pyinstaller, run pyinstaller --onefile opencv.py, then move the file in dist/opencv to binaries folder +# After that, run this command: bun run target:triple, which adds your architecture to the binary name +# When running the program, go to http://127.0.0.1:5000/video_feed to see the camera + +from flask import Flask, Response, redirect +import cv2 + +import threading +import sys +import time +import os + +# Shuts down server if windows are manually closed opposed to CTRL+C +def listen_for_shutdown(): + for line in sys.stdin: + if line.strip() == "sidecar shutdown": + print("[flask] Shutdown command received.") + cap.release() + os._exit(0) + +app = Flask(__name__) + +cap = cv2.VideoCapture(0) # Might need to change this to 1 depending on your camera / OS + +def gen_frames(): # generate frame by frame from camera + while(cap.isOpened()): + # Capture frame-by-frame + ret, frame = cap.read() # read the camera frame + if ret: + _, buffer = cv2.imencode('.jpg', frame) + frame = buffer.tobytes() + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') # concat frame one by one and show result + else: + break + cap.release() + cv2.destroyAllWindows() + +@app.route('/video_feed') +def video_feed(): + return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') + +@app.route('/') +def index(): + """Redirect to video feed.""" + return redirect('/video_feed') + +if __name__ == '__main__': + threading.Thread(target=listen_for_shutdown, daemon=True).start() + app.run(host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 45e1ae9..104bc56 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,6 +5,104 @@ use taurpc::Router; mod missions; use missions::api::{MissionApi, MissionApiImpl}; +use std::sync::{Arc, Mutex}; +use tauri::{Emitter, Manager, RunEvent}; +use tauri_plugin_shell::process::{CommandChild, CommandEvent}; +use tauri_plugin_shell::ShellExt; + +fn spawn_opencv_sidecar(app_handle: tauri::AppHandle) -> Result<(), String> { + // Check if a sidecar process already exists + if let Some(state) = app_handle.try_state::>>>() { + let child_process = state.lock().unwrap(); + if child_process.is_some() { + println!("[tauri] Sidecar is already running. Skipping spawn."); + return Ok(()); + } + } + + // Spawn sidecar (sidecar function only expects the filename, not the whole path configured in externalBin) + let sidecar_command = app_handle + .shell() + .sidecar("opencv") + .map_err(|e| e.to_string())?; + let (mut rx, child) = sidecar_command.spawn().map_err(|e| e.to_string())?; + + // Store the child process in the app state + if let Some(state) = app_handle.try_state::>>>() { + *state.lock().unwrap() = Some(child); + } else { + return Err("Failed to access app state".to_string()); + } + + // Spawn an async task to handle sidecar communication + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + println!("Sidecar stdout: {}", line); + + // Emit the line to the frontend + app_handle + .emit("sidecar-stdout", line.to_string()) + .expect("Failed to emit sidecar stdout event"); + } + + CommandEvent::Stderr(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + eprintln!("Sidecar stderr: {}", line); + // Emit the error line to the frontend + app_handle + .emit("sidecar-stderr", line.to_string()) + .expect("Failed to emit sidecar stderr event"); + } + _ => {} + } + } + }); + + Ok(()) +} + +#[tauri::command] +fn start_sidecar(app_handle: tauri::AppHandle) -> Result { + println!("[tauri] Received command to start sidecar."); + spawn_opencv_sidecar(app_handle)?; + Ok("Sidecar spawned and monitoring started.".to_string()) +} + +#[tauri::command] +fn shutdown_sidecar(app_handle: tauri::AppHandle) -> Result { + println!("[tauri] Received command to shutdown sidecar."); + // Access the sidecar process state + if let Some(state) = app_handle.try_state::>>>() { + let mut child_process = state + .lock() + .map_err(|_| "[tauri] Failed to acquire lock on sidecar process.")?; + + if let Some(mut process) = child_process.take() { + let command = "sidecar shutdown\n"; // Add newline to signal the end of the command + + // Attempt to write the command to the sidecar's stdin + if let Err(err) = process.write(command.as_bytes()) { + println!("[tauri] Failed to write to sidecar stdin: {}", err); + + // Restore the process reference if shutdown fails + *child_process = Some(process); + return Err(format!("Failed to write to sidecar stdin: {}", err)); + } + + println!("[tauri] Sent 'sidecar shutdown' command to sidecar."); + Ok("'sidecar shutdown' command sent.".to_string()) + } else { + println!("[tauri] No active sidecar process to shutdown."); + Err("No active sidecar process to shutdown.".to_string()) + } + } else { + Err("Sidecar process state not found.".to_string()) + } +} + #[tokio::main] async fn main() { // Initialize apis here @@ -14,7 +112,56 @@ async fn main() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) + .setup(|app| { + // Store the initial sidecar process in the app state + app.manage(Arc::new(Mutex::new(None::))); + // Clone the app handle for use elsewhere + let app_handle = app.handle(); + // Spawn the Python sidecar on startup + println!("[tauri] Creating sidecar..."); + spawn_opencv_sidecar(app_handle.clone()).ok(); + println!("[tauri] Sidecar spawned and monitoring started."); + Ok(()) + }) + // Register the shutdown_server command + .invoke_handler(tauri::generate_handler![start_sidecar, shutdown_sidecar,]) .invoke_handler(router.into_handler()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .build(tauri::generate_context!()) + .expect("Error while running tauri application") + .run(|app_handle, event| match event { + // Ensure the Python sidecar is killed when the app is closed + RunEvent::ExitRequested { .. } => { + if let Some(child_process) = + app_handle.try_state::>>>() + { + if let Ok(mut child) = child_process.lock() { + if let Some(process) = child.as_mut() { + // Send msg via stdin to sidecar where it self terminates + let command = "sidecar shutdown\n"; + let buf: &[u8] = command.as_bytes(); + let _ = process.write(buf); + + // Force kill the process after a short delay to ensure cleanup + std::thread::sleep(std::time::Duration::from_millis(500)); + if let Some(process) = child.take() { + let _ = process.kill(); + } + + // TODO: This is kind of messy, find a better way to clear, preferably cross platform + // Additional cleanup for Windows + #[cfg(target_os = "windows")] + { + use std::process::Command; + let _ = Command::new("taskkill") + .args(["/F", "/IM", "opencv.exe"]) + .output(); + } + + println!("[tauri] Sidecar closed."); + } + } + } + } + _ => {} + }); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cfa0444..dd27732 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,7 +14,8 @@ "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" - ] + ], + "externalBin": ["binaries/opencv"] }, "productName": "Interface", "mainBinaryName": "Interface",