Implement autoupdater functionality and enhance icon management

- Added a new UpdateNotification component to inform users about available updates and facilitate installation.
- Integrated useAutoUpdater hook for managing update checks and installations.
- Updated package.json with new build and generate scripts for release preparation and key generation.
- Enhanced tauri configuration to support autoupdater with signing keys and endpoints.
- Created scripts for preparing signed releases and updating icons, ensuring a streamlined workflow for asset management.
- Added detailed documentation for autoupdater setup and icon update workflow.
This commit is contained in:
Jamie Pine
2026-01-25 15:38:31 -08:00
parent 0e38d47855
commit 7d1ee4c1b9
65 changed files with 1624 additions and 55 deletions
+5
View File
@@ -99,6 +99,8 @@ jobs:
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: tauri
tagName: v__VERSION__
@@ -111,6 +113,9 @@ jobs:
- **macOS**: Download the `.dmg` file
- **Windows**: Download the `.msi` installer
- **Linux**: Download the `.AppImage` or `.deb` package
The app includes automatic updates - future updates will be installed automatically.
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
includeUpdaterJson: true
+34 -36
View File
@@ -2,6 +2,7 @@ import { Download, Play, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { CircleButton } from '@/components/ui/circle-button';
import {
Table,
TableBody,
@@ -29,6 +30,8 @@ export function HistoryTable() {
const setAudio = usePlayerStore((state) => state.setAudio);
const currentAudioId = usePlayerStore((state) => state.audioId);
const isPlaying = usePlayerStore((state) => state.isPlaying);
const audioUrl = usePlayerStore((state) => state.audioUrl);
const isPlayerVisible = !!audioUrl;
const handlePlay = (audioId: string, text: string) => {
const audioUrl = apiClient.getAudioUrl(audioId);
@@ -67,16 +70,19 @@ export function HistoryTable() {
</div>
) : (
<>
<div className="flex-1 min-h-0 overflow-y-auto border rounded-md max-h-[calc(100vh-280px)]">
<Table>
<div className={cn(
"flex-1 min-h-0 overflow-y-auto border rounded-md overflow-x-hidden",
isPlayerVisible && "max-h-[calc(100vh-280px)]"
)}>
<Table className="w-full table-fixed">
<TableHeader className="sticky top-0 bg-background z-10">
<TableRow>
<TableHead>Text</TableHead>
<TableHead>Profile</TableHead>
<TableHead>Language</TableHead>
<TableHead>Duration</TableHead>
<TableHead>Created</TableHead>
<TableHead className="text-right">Actions</TableHead>
<TableHead className="w-[40%]">Text</TableHead>
<TableHead className="w-[15%]">Profile</TableHead>
<TableHead className="w-[10%]">Language</TableHead>
<TableHead className="w-[10%]">Duration</TableHead>
<TableHead className="w-[15%]">Created</TableHead>
<TableHead className="w-[10%] text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -85,45 +91,37 @@ export function HistoryTable() {
return (
<TableRow
key={gen.id}
className={cn(isCurrentlyPlaying && 'bg-muted/50')}
className={cn(
isCurrentlyPlaying && 'bg-muted/50',
'cursor-pointer'
)}
onClick={() => handlePlay(gen.id, gen.text)}
>
<TableCell className="max-w-[200px] truncate">{gen.text}</TableCell>
<TableCell>{gen.profile_name}</TableCell>
<TableCell className="truncate">{gen.text}</TableCell>
<TableCell className="truncate">{gen.profile_name}</TableCell>
<TableCell>
<Badge variant="outline">{gen.language}</Badge>
<Badge variant="outline" className="text-xs">{gen.language}</Badge>
</TableCell>
<TableCell>{formatDuration(gen.duration)}</TableCell>
<TableCell className="text-sm">{formatDate(gen.created_at)}</TableCell>
<TableCell className="text-sm">{formatDuration(gen.duration)}</TableCell>
<TableCell className="text-xs text-muted-foreground/60">{formatDate(gen.created_at)}</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
<div className="flex justify-end gap-0.5" onClick={(e) => e.stopPropagation()}>
<CircleButton
icon={Play}
onClick={() => handlePlay(gen.id, gen.text)}
aria-label="Play audio"
className={
currentAudioId === gen.id && isPlaying ? 'text-primary' : ''
}
>
<Play className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
/>
<CircleButton
icon={Download}
onClick={() => handleDownload(gen.id, gen.text)}
aria-label="Download audio"
>
<Download className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
/>
<CircleButton
icon={Trash2}
onClick={() => deleteGeneration.mutate(gen.id)}
disabled={deleteGeneration.isPending}
aria-label="Delete generation"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
/>
</div>
</TableCell>
</TableRow>
+69
View File
@@ -0,0 +1,69 @@
import { useAutoUpdater } from '../hooks/useAutoUpdater';
import { Button } from './ui/button';
import { Card } from './ui/card';
import { Progress } from './ui/progress';
export function UpdateNotification() {
const { status, checkForUpdates, downloadAndInstall } = useAutoUpdater(true);
if (status.error) {
return null;
}
if (!status.available && !status.checking) {
return null;
}
if (status.checking) {
return (
<Card className="p-4 mb-4">
<div className="flex items-center gap-3">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full" />
<span className="text-sm">Checking for updates...</span>
</div>
</Card>
);
}
if (status.available) {
return (
<Card className="p-4 mb-4 border-primary">
<div className="space-y-3">
<div>
<h3 className="font-semibold">Update Available</h3>
<p className="text-sm text-muted-foreground">
Version {status.version} is ready to install
</p>
</div>
{status.downloading && (
<div className="space-y-2">
<p className="text-sm">Downloading update...</p>
<Progress />
</div>
)}
{status.installing && (
<div className="space-y-2">
<p className="text-sm">Installing update...</p>
<p className="text-xs text-muted-foreground">App will restart automatically</p>
</div>
)}
{!status.downloading && !status.installing && (
<div className="flex gap-2">
<Button onClick={downloadAndInstall} size="sm">
Install Now
</Button>
<Button onClick={() => window.location.reload()} variant="outline" size="sm">
Later
</Button>
</div>
)}
</div>
</Card>
);
}
return null;
}
+104
View File
@@ -0,0 +1,104 @@
import { useEffect, useState } from 'react';
import { check, type Update } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
export interface UpdateStatus {
checking: boolean;
available: boolean;
version?: string;
downloading: boolean;
installing: boolean;
error?: string;
}
export function useAutoUpdater(checkOnMount = false) {
const [status, setStatus] = useState<UpdateStatus>({
checking: false,
available: false,
downloading: false,
installing: false,
});
const [update, setUpdate] = useState<Update | null>(null);
const checkForUpdates = async () => {
try {
setStatus((prev) => ({ ...prev, checking: true, error: undefined }));
const foundUpdate = await check();
if (foundUpdate?.available) {
setUpdate(foundUpdate);
setStatus({
checking: false,
available: true,
version: foundUpdate.version,
downloading: false,
installing: false,
});
} else {
setStatus({
checking: false,
available: false,
downloading: false,
installing: false,
});
}
} catch (error) {
setStatus({
checking: false,
available: false,
downloading: false,
installing: false,
error: error instanceof Error ? error.message : 'Failed to check for updates',
});
}
};
const downloadAndInstall = async () => {
if (!update) return;
try {
setStatus((prev) => ({ ...prev, downloading: true, error: undefined }));
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
setStatus((prev) => ({ ...prev, downloading: true }));
break;
case 'Progress':
console.log(`Downloaded ${event.data.chunkLength} bytes`);
break;
case 'Finished':
setStatus((prev) => ({
...prev,
downloading: false,
installing: true,
}));
break;
}
});
await relaunch();
} catch (error) {
setStatus((prev) => ({
...prev,
downloading: false,
installing: false,
error: error instanceof Error ? error.message : 'Failed to install update',
}));
}
};
useEffect(() => {
if (checkOnMount) {
checkForUpdates();
}
}, [checkOnMount]);
return {
status,
checkForUpdates,
downloadAndInstall,
};
}
+224
View File
@@ -0,0 +1,224 @@
# Tauri v2 Autoupdater Setup
The autoupdater has been configured for this project. Follow these steps to complete the setup.
## 1. Generate Signing Keys
Run this command to generate your signing keypair:
```bash
cd tauri && bun tauri signer generate -w ~/.tauri/voicebox.key
```
This creates:
- **Private key**: `~/.tauri/voicebox.key` (keep this secret!)
- **Public key**: `~/.tauri/voicebox.key.pub`
## 2. Update Configuration
Copy the content from `~/.tauri/voicebox.key.pub` and replace the placeholder in `tauri/src-tauri/tauri.conf.json`:
```json
{
"plugins": {
"updater": {
"pubkey": "PASTE_PUBLIC_KEY_CONTENT_HERE",
"endpoints": [
"https://github.com/YOUR_USERNAME/voicebox/releases/latest/download/latest.json"
]
}
}
}
```
Update the endpoint URL with your actual GitHub username/organization.
## 3. Building with Signatures
When building releases, set these environment variables:
**macOS/Linux:**
```bash
export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/voicebox.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
bun run build
```
**Windows PowerShell:**
```powershell
$env:TAURI_SIGNING_PRIVATE_KEY = Get-Content ~/.tauri/voicebox.key -Raw
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = ""
bun run build
```
## 4. GitHub Release Setup
When you create a GitHub release, the build process will generate:
- Installers for each platform
- `.sig` signature files
- `latest.json` update manifest
### Manual Release Process
1. Build the app with signing keys set
2. Create a new GitHub release
3. Upload all files from `tauri/src-tauri/target/release/bundle/`
4. Create `latest.json` in your release assets:
```json
{
"version": "0.2.0",
"notes": "Bug fixes and improvements",
"pub_date": "2026-01-25T12:00:00Z",
"platforms": {
"darwin-aarch64": {
"signature": "CONTENT_FROM_.app.tar.gz.sig",
"url": "https://github.com/YOUR_USERNAME/voicebox/releases/download/v0.2.0/voicebox_0.2.0_aarch64.dmg"
},
"darwin-x86_64": {
"signature": "CONTENT_FROM_.app.tar.gz.sig",
"url": "https://github.com/YOUR_USERNAME/voicebox/releases/download/v0.2.0/voicebox_0.2.0_x64.dmg"
},
"linux-x86_64": {
"signature": "CONTENT_FROM_.AppImage.sig",
"url": "https://github.com/YOUR_USERNAME/voicebox/releases/download/v0.2.0/voicebox_0.2.0_amd64.AppImage"
},
"windows-x86_64": {
"signature": "CONTENT_FROM_.msi.sig",
"url": "https://github.com/YOUR_USERNAME/voicebox/releases/download/v0.2.0/voicebox_0.2.0_x64_en-US.msi"
}
}
}
```
### Automated GitHub Actions (Recommended)
Create `.github/workflows/release.yml`:
```yaml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
strategy:
matrix:
platform: [macos-latest, ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies (Ubuntu)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install dependencies
run: bun install
- name: Build
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bun run build
- name: Upload Release
uses: softprops/action-gh-release@v1
with:
files: tauri/src-tauri/target/release/bundle/**/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
Add your private key to GitHub secrets:
- Go to Settings → Secrets and variables → Actions
- Add `TAURI_SIGNING_PRIVATE_KEY` with the content of `~/.tauri/voicebox.key`
- Add `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` (empty string if no password)
## 5. Implementing Update Check in Frontend
Add the updater package:
```bash
bun add @tauri-apps/plugin-updater
```
Example implementation:
```typescript
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
async function checkForUpdates() {
try {
const update = await check();
if (update?.available) {
console.log(`Update available: ${update.version}`);
// Show UI to user
const shouldUpdate = confirm(
`Version ${update.version} is available. Install now?`
);
if (shouldUpdate) {
await update.downloadAndInstall();
await relaunch();
}
}
} catch (error) {
console.error('Update check failed:', error);
}
}
// Check on app startup
checkForUpdates();
// Or add a manual check button
```
## Security Notes
- Never commit your private key to version control
- Store private keys securely (use GitHub secrets for CI/CD)
- The public key in `tauri.conf.json` is safe to commit
- Updates are cryptographically verified before installation
- HTTP endpoints are blocked by default (HTTPS only)
## Testing Updates
1. Build version 0.1.0 and install it
2. Update version in `tauri.conf.json` to 0.2.0
3. Build version 0.2.0 with signatures
4. Create a local server or GitHub release with `latest.json`
5. Run version 0.1.0 and trigger update check
6. Verify update downloads and installs correctly
## Troubleshooting
**"Invalid signature" error:**
- Verify public key matches the private key used to sign
- Ensure signature files (.sig) are uploaded correctly
**"No update available" when one exists:**
- Check endpoint URL is correct
- Verify `latest.json` format matches specification
- Ensure version in latest.json is higher than current version
**Build fails with signing:**
- Confirm environment variables are set correctly
- Check private key file exists and is readable
- Verify private key format (should start with `dW50cnVzdGVkIGNvbW1lbnQ6`)
+150
View File
@@ -0,0 +1,150 @@
# Icon Update Workflow for voicebox
## Prerequisites
- Xcode Command Line Tools installed (`xcode-select --install`)
- Your icon designed in Icon Composer and saved as `voicebox.icon` in the project root
## Directory Structure
```
voicebox/
├── voicebox.icon/ # macOS 26 Liquid Glass icon bundle
│ ├── icon.json
│ └── Assets/
│ └── Voicebox.png # Your source image
├── tauri/
│ ├── assets/
│ │ └── voicebox_exports/ # Your 1024x1024 exports
│ └── src-tauri/
│ ├── gen/ # Auto-generated at build time
│ │ ├── Assets.car # Liquid Glass assets
│ │ ├── voicebox.icns # Generated fallback icon
│ │ └── partial.plist
│ └── icons/ # Tauri fallback icons (all platforms)
│ ├── icon.icns
│ ├── icon.ico
│ ├── 32x32.png
│ ├── 128x128.png
│ └── ...
```
## Step 1: Update the Liquid Glass Icon
Edit `voicebox.icon/` in Icon Composer (or manually update `icon.json` and `Assets/`).
The build script (`build.rs`) automatically compiles this during `cargo build`.
## Step 2: Regenerate Fallback Icons
After updating the `.icon` bundle, regenerate the fallback icons:
```bash
cd tauri/src-tauri
# Trigger rebuild to generate new icns
cargo build
# Copy the generated icns to icons folder
cp gen/voicebox.icns icons/icon.icns
# Generate PNGs from the icns
sips -s format png -z 32 32 gen/voicebox.icns --out icons/32x32.png
sips -s format png -z 64 64 gen/voicebox.icns --out icons/64x64.png
sips -s format png -z 128 128 gen/voicebox.icns --out icons/128x128.png
sips -s format png -z 256 256 gen/voicebox.icns --out icons/[email protected]
sips -s format png -z 512 512 gen/voicebox.icns --out icons/icon.png
# Windows Square logos
for size in 30 44 71 89 107 142 150 284 310; do
sips -s format png -z $size $size gen/voicebox.icns --out "icons/Square${size}x${size}Logo.png"
done
```
## Step 3: Rebuild the App
```bash
cd tauri && bun run tauri build
```
## Step 4: Clear Icon Cache (if icons don't update)
```bash
sudo rm -rf /Library/Caches/com.apple.iconservices.store
sudo killall Finder
sudo killall Dock
```
---
## How It Works
| File | Purpose |
|------|---------|
| `voicebox.icon/` | Source for macOS 26 Liquid Glass icon |
| `build.rs` | Compiles `.icon``Assets.car` + `voicebox.icns` at build time |
| `gen/Assets.car` | Liquid Glass assets (macOS 26+) |
| `gen/voicebox.icns` | Auto-generated fallback icon |
| `icons/icon.icns` | Tauri's fallback for older macOS |
| `icons/*.png` | Tauri's fallback for other platforms |
| `Info.plist` | Points to `voicebox` as icon name |
| `tauri.conf.json` | Bundles `gen/*` to Resources root |
## Key Config Files
### `build.rs` — Compiles the icon
```rust
xcrun actool --app-icon voicebox ... voicebox.icon
```
### `tauri.conf.json` — Bundles generated assets to Resources root
```json
"resources": {
"gen/Assets.car": "./",
"gen/voicebox.icns": "./",
"gen/partial.plist": "./"
}
```
### `Info.plist` — Tells macOS which icon to use
```xml
<key>CFBundleIconFile</key>
<string>voicebox</string>
<key>CFBundleIconName</key>
<string>voicebox</string>
```
## Quick Reference Script
Save this as `scripts/update-icons.sh`:
```bash
#!/bin/bash
set -e
cd "$(dirname "$0")/../tauri/src-tauri"
echo "Building to generate new icons..."
cargo build
echo "Copying icns to icons folder..."
cp gen/voicebox.icns icons/icon.icns
echo "Generating PNG icons..."
sips -s format png -z 32 32 gen/voicebox.icns --out icons/32x32.png
sips -s format png -z 64 64 gen/voicebox.icns --out icons/64x64.png
sips -s format png -z 128 128 gen/voicebox.icns --out icons/128x128.png
sips -s format png -z 256 256 gen/voicebox.icns --out icons/[email protected]
sips -s format png -z 512 512 gen/voicebox.icns --out icons/icon.png
echo "Generating Windows Square logos..."
for size in 30 44 71 89 107 142 150 284 310; do
sips -s format png -z $size $size gen/voicebox.icns --out "icons/Square${size}x${size}Logo.png"
done
echo "Done! Icons updated."
echo "Run 'bun run tauri build' to rebuild the app."
```
Make it executable: `chmod +x scripts/update-icons.sh`
+3
View File
@@ -16,8 +16,11 @@
"build": "cd tauri && bun run tauri build",
"build:web": "cd web && bun run build",
"build:landing": "cd landing && bun run build",
"build:release": "./scripts/prepare-release.sh",
"generate:api": "./scripts/generate-api.sh",
"generate:keys": "cd tauri && bun tauri signer generate -w ~/.tauri/voicebox.key",
"build:server": "./scripts/build-server.sh",
"update:icons": "./scripts/update-icons.sh",
"lint": "biome lint .",
"lint:fix": "biome lint --write .",
"format": "biome format --write .",
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# Script to prepare a signed release build
# Usage: ./scripts/prepare-release.sh
set -e
echo "🔑 Checking for signing keys..."
if [ ! -f ~/.tauri/voicebox.key ]; then
echo "❌ Private key not found at ~/.tauri/voicebox.key"
echo "Run: cd tauri && bun tauri signer generate -w ~/.tauri/voicebox.key"
exit 1
fi
if [ ! -f ~/.tauri/voicebox.key.pub ]; then
echo "❌ Public key not found at ~/.tauri/voicebox.key.pub"
exit 1
fi
echo "✅ Signing keys found"
echo ""
# Check if public key is in tauri.conf.json
if grep -q "REPLACE_WITH_YOUR_PUBLIC_KEY" tauri/src-tauri/tauri.conf.json; then
echo "⚠️ Public key not configured in tauri.conf.json"
echo ""
echo "Add this to tauri/src-tauri/tauri.conf.json:"
echo ""
cat ~/.tauri/voicebox.key.pub
echo ""
exit 1
fi
echo "🔧 Setting up environment..."
export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/voicebox.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
echo "✅ Environment configured"
echo ""
echo "📦 Building release..."
echo ""
bun run build
echo ""
echo "✅ Release build complete!"
echo ""
echo "📂 Bundles created in: tauri/src-tauri/target/release/bundle/"
echo ""
echo "Next steps:"
echo "1. Create a GitHub release"
echo "2. Upload all files from the bundle directory"
echo "3. Create latest.json with update metadata"
echo ""
+315
View File
@@ -0,0 +1,315 @@
#!/bin/bash
set -e
# Complete Icon Update Script
# Updates both Liquid Glass icon bundle AND all platform fallback icons from exports
cd "$(dirname "$0")/.."
EXPORTS_DIR="tauri/assets/voicebox_exports"
ICON_BUNDLE="tauri/assets/voicebox.icon"
ASSETS_DIR="$ICON_BUNDLE/Assets"
ICONS_DIR="tauri/src-tauri/icons"
SOURCE_ICON="$EXPORTS_DIR/[email protected]"
echo "🎨 Updating all Voicebox icons from exports..."
echo ""
# Check if source exists
if [ ! -f "$SOURCE_ICON" ]; then
echo "Error: Source icon not found at $SOURCE_ICON"
exit 1
fi
# ============================================
# PART 1: Update voicebox.icon Bundle
# ============================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📦 Part 1: Updating Liquid Glass Icon Bundle"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
mkdir -p "$ASSETS_DIR"
echo "Copying appearance variants..."
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox.png"
echo " ✓ Default"
[ -f "$EXPORTS_DIR/[email protected]" ] && {
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox-Dark.png"
echo " ✓ Dark"
}
[ -f "$EXPORTS_DIR/[email protected]" ] && {
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox-ClearLight.png"
echo " ✓ Clear Light"
}
[ -f "$EXPORTS_DIR/[email protected]" ] && {
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox-ClearDark.png"
echo " ✓ Clear Dark"
}
[ -f "$EXPORTS_DIR/[email protected]" ] && {
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox-TintedLight.png"
echo " ✓ Tinted Light"
}
[ -f "$EXPORTS_DIR/[email protected]" ] && {
cp "$EXPORTS_DIR/[email protected]" "$ASSETS_DIR/Voicebox-TintedDark.png"
echo " ✓ Tinted Dark"
}
echo ""
echo "Updating icon.json..."
cat > "$ICON_BUNDLE/icon.json" << 'EOF'
{
"fill" : "automatic",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox.png",
"name" : "Voicebox",
"position" : {
"scale" : 0.37,
"translation-in-points" : [
-0.12338405356129378,
297.27705195772353
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"appearances" : [
{
"appearance" : "light",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox.png",
"name" : "Voicebox"
}
]
}
]
},
{
"appearance" : "dark",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-Dark.png",
"name" : "Voicebox Dark"
}
]
}
]
},
{
"appearance" : "light",
"contrast" : "clear",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-ClearLight.png",
"name" : "Voicebox Clear Light"
}
]
}
]
},
{
"appearance" : "dark",
"contrast" : "clear",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-ClearDark.png",
"name" : "Voicebox Clear Dark"
}
]
}
]
},
{
"appearance" : "light",
"contrast" : "tinted",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-TintedLight.png",
"name" : "Voicebox Tinted Light"
}
]
}
]
},
{
"appearance" : "dark",
"contrast" : "tinted",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-TintedDark.png",
"name" : "Voicebox Tinted Dark"
}
]
}
]
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
EOF
echo " ✓ icon.json updated"
echo ""
# ============================================
# PART 2: Generate Platform Fallback Icons
# ============================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🖼️ Part 2: Generating Platform Fallback Icons"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
mkdir -p "$ICONS_DIR"
# macOS & Desktop Icons
echo "Generating macOS/Desktop icons..."
sips -s format png -z 32 32 "$SOURCE_ICON" --out "$ICONS_DIR/32x32.png" 2>/dev/null
sips -s format png -z 64 64 "$SOURCE_ICON" --out "$ICONS_DIR/64x64.png" 2>/dev/null
sips -s format png -z 128 128 "$SOURCE_ICON" --out "$ICONS_DIR/128x128.png" 2>/dev/null
sips -s format png -z 256 256 "$SOURCE_ICON" --out "$ICONS_DIR/[email protected]" 2>/dev/null
sips -s format png -z 512 512 "$SOURCE_ICON" --out "$ICONS_DIR/icon.png" 2>/dev/null
# Generate ICNS
echo "Generating icon.icns..."
mkdir -p /tmp/voicebox-iconset.iconset
sips -s format png -z 16 16 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/icon_16x16.png 2>/dev/null
sips -s format png -z 32 32 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/[email protected] 2>/dev/null
sips -s format png -z 32 32 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/icon_32x32.png 2>/dev/null
sips -s format png -z 64 64 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/[email protected] 2>/dev/null
sips -s format png -z 128 128 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/icon_128x128.png 2>/dev/null
sips -s format png -z 256 256 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/[email protected] 2>/dev/null
sips -s format png -z 256 256 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/icon_256x256.png 2>/dev/null
sips -s format png -z 512 512 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/[email protected] 2>/dev/null
sips -s format png -z 512 512 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/icon_512x512.png 2>/dev/null
sips -s format png -z 1024 1024 "$SOURCE_ICON" --out /tmp/voicebox-iconset.iconset/[email protected] 2>/dev/null
iconutil -c icns /tmp/voicebox-iconset.iconset -o "$ICONS_DIR/icon.icns"
rm -rf /tmp/voicebox-iconset.iconset
# Windows Square Logos
echo "Generating Windows icons..."
for size in 30 44 71 89 107 142 150 284 310; do
sips -s format png -z $size $size "$SOURCE_ICON" --out "$ICONS_DIR/Square${size}x${size}Logo.png" 2>/dev/null
done
sips -s format png -z 50 50 "$SOURCE_ICON" --out "$ICONS_DIR/StoreLogo.png" 2>/dev/null
# iOS Icons
echo "Generating iOS icons..."
mkdir -p "$ICONS_DIR/ios"
declare -A ios_sizes=(
["[email protected]"]="20"
["[email protected]"]="40"
["[email protected]"]="40"
["[email protected]"]="60"
["[email protected]"]="29"
["[email protected]"]="58"
["[email protected]"]="58"
["[email protected]"]="87"
["[email protected]"]="40"
["[email protected]"]="80"
["[email protected]"]="80"
["[email protected]"]="120"
["[email protected]"]="120"
["[email protected]"]="180"
["[email protected]"]="76"
["[email protected]"]="152"
["[email protected]"]="167"
["[email protected]"]="1024"
)
for filename in "${!ios_sizes[@]}"; do
size="${ios_sizes[$filename]}"
sips -s format png -z $size $size "$SOURCE_ICON" --out "$ICONS_DIR/ios/$filename" 2>/dev/null
done
# Android Icons
echo "Generating Android icons..."
mkdir -p "$ICONS_DIR/android/mipmap-mdpi"
mkdir -p "$ICONS_DIR/android/mipmap-hdpi"
mkdir -p "$ICONS_DIR/android/mipmap-xhdpi"
mkdir -p "$ICONS_DIR/android/mipmap-xxhdpi"
mkdir -p "$ICONS_DIR/android/mipmap-xxxhdpi"
sips -s format png -z 48 48 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-mdpi/ic_launcher.png" 2>/dev/null
sips -s format png -z 48 48 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-mdpi/ic_launcher_round.png" 2>/dev/null
sips -s format png -z 48 48 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-mdpi/ic_launcher_foreground.png" 2>/dev/null
sips -s format png -z 72 72 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-hdpi/ic_launcher.png" 2>/dev/null
sips -s format png -z 72 72 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-hdpi/ic_launcher_round.png" 2>/dev/null
sips -s format png -z 72 72 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-hdpi/ic_launcher_foreground.png" 2>/dev/null
sips -s format png -z 96 96 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xhdpi/ic_launcher.png" 2>/dev/null
sips -s format png -z 96 96 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xhdpi/ic_launcher_round.png" 2>/dev/null
sips -s format png -z 96 96 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xhdpi/ic_launcher_foreground.png" 2>/dev/null
sips -s format png -z 144 144 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxhdpi/ic_launcher.png" 2>/dev/null
sips -s format png -z 144 144 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxhdpi/ic_launcher_round.png" 2>/dev/null
sips -s format png -z 144 144 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxhdpi/ic_launcher_foreground.png" 2>/dev/null
sips -s format png -z 192 192 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxxhdpi/ic_launcher.png" 2>/dev/null
sips -s format png -z 192 192 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxxhdpi/ic_launcher_round.png" 2>/dev/null
sips -s format png -z 192 192 "$SOURCE_ICON" --out "$ICONS_DIR/android/mipmap-xxxhdpi/ic_launcher_foreground.png" 2>/dev/null
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ All icons updated successfully!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Updated:"
echo " ✓ Liquid Glass icon bundle with all appearance variants"
echo " ✓ macOS/Desktop fallback icons"
echo " ✓ Windows Square logos"
echo " ✓ iOS AppIcons (18 sizes)"
echo " ✓ Android mipmap icons (5 densities)"
echo ""
echo "Next: Rebuild the app with 'cd tauri && bun run tauri build'"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

+97 -1
View File
@@ -27,10 +27,106 @@
}
}
],
"appearances" : [
{
"appearance" : "light",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox.png",
"name" : "Voicebox"
}
]
}
]
},
{
"appearance" : "dark",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-Dark.png",
"name" : "Voicebox Dark"
}
]
}
]
},
{
"appearance" : "light",
"contrast" : "clear",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-ClearLight.png",
"name" : "Voicebox Clear Light"
}
]
}
]
},
{
"appearance" : "dark",
"contrast" : "clear",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-ClearDark.png",
"name" : "Voicebox Clear Dark"
}
]
}
]
},
{
"appearance" : "light",
"contrast" : "tinted",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-TintedLight.png",
"name" : "Voicebox Tinted Light"
}
]
}
]
},
{
"appearance" : "dark",
"contrast" : "tinted",
"groups" : [
{
"layers" : [
{
"blend-mode" : "normal",
"glass" : true,
"image-name" : "Voicebox-TintedDark.png",
"name" : "Voicebox Tinted Dark"
}
]
}
]
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+428 -2
View File
@@ -47,6 +47,15 @@ version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "atk"
version = "0.18.2"
@@ -285,6 +294,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.43"
@@ -488,6 +503,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "derive_more"
version = "0.99.20"
@@ -674,6 +700,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
version = "0.3.7"
@@ -693,6 +725,17 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
dependencies = [
"cfg-if",
"libc",
"libredox",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
@@ -969,8 +1012,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -980,9 +1025,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasip2",
"wasm-bindgen",
]
[[package]]
@@ -1235,6 +1282,23 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
]
[[package]]
name = "hyper-util"
version = "0.1.19"
@@ -1628,8 +1692,15 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags 2.10.0",
"libc",
"redox_syscall 0.7.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.1"
@@ -1651,6 +1722,12 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mac"
version = "0.1.1"
@@ -1709,6 +1786,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -1991,6 +2074,18 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@@ -2076,6 +2171,20 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "pango"
version = "0.18.3"
@@ -2119,7 +2228,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.5.18",
"smallvec",
"windows-link 0.2.1",
]
@@ -2421,6 +2530,61 @@ dependencies = [
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]]
name = "quote"
version = "1.0.44"
@@ -2461,6 +2625,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -2481,6 +2655,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -2499,6 +2683,15 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -2532,6 +2725,15 @@ dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "redox_users"
version = "0.5.2"
@@ -2606,16 +2808,21 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@@ -2625,8 +2832,29 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -2636,6 +2864,54 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -3026,7 +3302,7 @@ dependencies = [
"objc2-foundation",
"objc2-quartz-core",
"raw-window-handle",
"redox_syscall",
"redox_syscall 0.5.18",
"tracing",
"wasm-bindgen",
"web-sys",
@@ -3096,6 +3372,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.7"
@@ -3213,6 +3495,17 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "tar"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -3371,6 +3664,38 @@ dependencies = [
"tokio",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]]
name = "tauri-runtime"
version = "2.9.2"
@@ -3472,6 +3797,19 @@ dependencies = [
"toml 0.9.11+spec-1.1.0",
]
[[package]]
name = "tempfile"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "tendril"
version = "0.4.3"
@@ -3564,6 +3902,21 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.49.0"
@@ -3592,6 +3945,16 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
@@ -3858,6 +4221,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
@@ -3928,6 +4297,7 @@ dependencies = [
"tauri",
"tauri-build",
"tauri-plugin-shell",
"tauri-plugin-updater",
"tokio",
]
@@ -4073,6 +4443,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webkit2gtk"
version = "2.0.1"
@@ -4117,6 +4497,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-roots"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webview2-com"
version = "0.38.2"
@@ -4347,6 +4736,15 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -4684,6 +5082,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.1"
@@ -4748,6 +5156,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.3"
@@ -4781,6 +5195,18 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.13.0",
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.16"
+3
View File
@@ -19,6 +19,9 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2.0"
[features]
# This feature is used for production builds or when `devPath` points to the filesystem
custom-protocol = ["tauri/custom-protocol"]
+2 -1
View File
@@ -10,6 +10,7 @@
"core:webview:allow-internal-toggle-devtools",
"shell:allow-open",
"shell:allow-execute",
"shell:allow-spawn"
"shell:allow-spawn",
"updater:default"
]
}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
{"default":{"identifier":"default","description":"Default permissions for voicebox","local":true,"permissions":["core:default","core:window:default","core:webview:default","core:webview:allow-internal-toggle-devtools","shell:allow-open","shell:allow-execute","shell:allow-spawn"],"platforms":["linux","macOS","windows"]}}
{"default":{"identifier":"default","description":"Default permissions for voicebox","local":true,"permissions":["core:default","core:window:default","core:webview:default","core:webview:allow-internal-toggle-devtools","shell:allow-open","shell:allow-execute","shell:allow-spawn","updater:default"],"platforms":["linux","macOS","windows"]}}
@@ -2419,6 +2419,60 @@
"type": "string",
"const": "shell:deny-stdin-write",
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
}
]
},
@@ -2419,6 +2419,60 @@
"type": "string",
"const": "shell:deny-stdin-write",
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
}
]
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 297 KiB

+16 -13
View File
@@ -132,6 +132,22 @@ pub fn run() {
.manage(ServerState {
child: Mutex::new(None),
})
.setup(|app| {
#[cfg(desktop)]
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
#[cfg(debug_assertions)]
{
// Get all windows and open devtools on the first one
if let Some((_, window)) = app.webview_windows().iter().next() {
window.open_devtools();
println!("Dev tools opened");
} else {
println!("No window found to open dev tools");
}
}
Ok(())
})
.invoke_handler(tauri::generate_handler![start_server, stop_server])
.on_window_event(|window, event| {
if let WindowEvent::CloseRequested { api, .. } = event {
@@ -177,19 +193,6 @@ pub fn run() {
});
}
})
.setup(|_app| {
#[cfg(debug_assertions)]
{
// Get all windows and open devtools on the first one
if let Some((_, window)) = _app.webview_windows().iter().next() {
window.open_devtools();
println!("Dev tools opened");
} else {
println!("No window found to open dev tools");
}
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+7
View File
@@ -12,6 +12,7 @@
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"externalBin": [
"binaries/voicebox-server"
],
@@ -56,6 +57,12 @@
"plugins": {
"shell": {
"open": true
},
"updater": {
"pubkey": "REPLACE_WITH_YOUR_PUBLIC_KEY",
"endpoints": [
"https://github.com/YOUR_USERNAME/voicebox/releases/latest/download/latest.json"
]
}
}
}