diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 1a12f45..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(ssh:*)", - "Bash(bun run lint)", - "Bash(bun run prisma:generate:*)", - "Bash(bun run build:bun:*)", - "WebSearch", - "Bash(bun add:*)", - "Bash(bun run tauri:*)", - "Bash(curl:*)", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\")", - "Bash(findstr:*)", - "Bash(cat:*)", - "Bash(chmod:*)", - "Bash(find:*)", - "Bash(grep:*)", - "WebFetch(domain:medium.com)", - "WebFetch(domain:henrywithu.com)", - "WebFetch(domain:hub.docker.com)", - "Bash(python3:*)", - "WebFetch(domain:www.npmjs.com)", - "WebFetch(domain:docs.strapi.io)", - "Bash(tablename)", - "Bash(\"\"\" OWNER TO renan; FROM pg_tables WHERE schemaname = public;\"\" | docker exec -i c95ebc27eb82 psql -U sistema -d strapi_blog\")", - "Bash(sequence_name)", - "Bash(\"\"\" OWNER TO renan; FROM information_schema.sequences WHERE sequence_schema = public;\"\" | docker exec -i c95ebc27eb82 psql -U sistema -d strapi_blog\")", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(cargo check:*)", - "Bash(bun run:*)", - "Bash(icacls \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\")", - "Bash(copy \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\" \"%TEMP%\\codex_key\")", - "Bash(icacls \"%TEMP%\\codex_key\" /inheritance:r /grant:r \"%USERNAME%:R\")", - "Bash(cmd /c \"echo %TEMP%\")", - "Bash(cmd /c \"dir \"\"%TEMP%\\codex_key\"\"\")", - "Bash(where:*)", - "Bash(ssh-keygen:*)", - "Bash(/c/Program\\ Files/Git/usr/bin/ssh:*)", - "Bash(npx convex deploy:*)", - "Bash(dir \"%LOCALAPPDATA%\\Raven\")", - "Bash(dir \"%APPDATA%\\Raven\")", - "Bash(dir \"%LOCALAPPDATA%\\com.raven.app\")", - "Bash(dir \"%APPDATA%\\com.raven.app\")", - "Bash(tasklist:*)", - "Bash(dir /s /b %LOCALAPPDATA%*raven*)", - "Bash(cmd /c \"tasklist | findstr /i raven\")", - "Bash(cmd /c \"dir /s /b %LOCALAPPDATA%\\*raven* 2>nul\")", - "Bash(powershell -Command \"Get-Process | Where-Object {$_ProcessName -like ''*raven*'' -or $_ProcessName -like ''*appsdesktop*''} | Select-Object ProcessName, Id\")", - "Bash(node:*)", - "Bash(bun scripts/test-all-emails.tsx:*)", - "Bash(bun scripts/send-test-react-email.tsx:*)", - "Bash(dir:*)", - "Bash(git reset:*)", - "Bash(npx convex:*)", - "Bash(bun tsc:*)", - "Bash(scp:*)", - "Bash(docker run:*)", - "Bash(cmd /c \"docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18\")", - "Bash(cmd /c \"docker ps -a --filter name=postgres-dev\")", - "Bash(cmd /c \"docker --version && docker ps -a\")", - "Bash(powershell -Command \"docker --version\")", - "Bash(powershell -Command \"docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18\")", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\" /b)", - "Bash(bunx prisma migrate:*)", - "Bash(bunx prisma db push:*)", - "Bash(bun run auth:seed:*)", - "Bash(set DATABASE_URL=postgresql://postgres:dev@localhost:5432/sistema_chamados:*)", - "Bash(bun tsx:*)", - "Bash(DATABASE_URL=\"postgresql://postgres:dev@localhost:5432/sistema_chamados\" bun tsx:*)", - "Bash(docker stop:*)", - "Bash(docker rm:*)", - "Bash(git commit -m \"$(cat <<''EOF''\nfeat(checklist): exibe descricao do template e do item no ticket\n\n- Adiciona campo templateDescription ao schema do checklist\n- Copia descricao do template ao aplicar checklist no ticket\n- Exibe ambas descricoes na visualizacao do ticket (template em italico)\n- Adiciona documentacao de desenvolvimento local (docs/LOCAL-DEV.md)\n- Corrige prisma-client.mjs para usar PostgreSQL em vez de SQLite\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n)\")", - "Bash(timeout 90 git push:*)", - "Bash(docker ps:*)", - "Bash(docker start:*)", - "Bash(docker inspect:*)", - "Bash(docker exec:*)", - "Bash(timeout 90 git push)", - "Bash(bun test:*)", - "Bash(git restore:*)", - "Bash(cd:*)", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\\src\\components\\ui\" /b)", - "Bash(timeout 120 bun:*)", - "Bash(bun run tauri:build:*)", - "Bash(git remote:*)", - "Bash(powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"D:/Projetos IA/sistema-de-chamados/scripts/test-windows-collection.ps1\")" - ] - } -} diff --git a/.env.example b/.env.example deleted file mode 100644 index 739bb8b..0000000 --- a/.env.example +++ /dev/null @@ -1,38 +0,0 @@ -NODE_ENV=development - -# Public app URL -NEXT_PUBLIC_APP_URL=http://localhost:3000 - -# Better Auth -BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET=your-secret-key-at-least-32-chars-long - -# Convex (dev server URL) -NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 -CONVEX_INTERNAL_URL=http://127.0.0.1:3210 -# Intervalo (ms) para aceitar token revogado ao sincronizar acessos remotos (opcional) -REMOTE_ACCESS_TOKEN_GRACE_MS=900000 -# Token interno opcional para o dashboard de saude (/admin/health) e queries internas -INTERNAL_HEALTH_TOKEN=dev-health-token -# Segredo para crons HTTP (reutilize em prod se preferir um unico token) -REPORTS_CRON_SECRET=reports-cron-secret -# Diretório para arquivamento local de tickets (JSONL/backup) -ARCHIVE_DIR=./archives - -# PostgreSQL database (versao 18) -# Para desenvolvimento local, use Docker: -# docker run -d --name postgres-chamados -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18 -DATABASE_URL=postgresql://postgres:dev@localhost:5432/sistema_chamados - -# SMTP Configuration (production values in docs/SMTP.md) -SMTP_HOST=smtp.c.inova.com.br -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=envio@rever.com.br -SMTP_PASS=CAAJQm6ZT6AUdhXRTDYu -SMTP_FROM_NAME=Sistema de Chamados -SMTP_FROM_EMAIL=envio@rever.com.br - -# Dev-only bypass to simplify local testing (do NOT enable in prod) -# DEV_BYPASS_AUTH=0 -# NEXT_PUBLIC_DEV_BYPASS_AUTH=0 diff --git a/.forgejo/workflows/ci-cd-web-desktop.yml b/.forgejo/workflows/ci-cd-web-desktop.yml deleted file mode 100644 index db80c21..0000000 --- a/.forgejo/workflows/ci-cd-web-desktop.yml +++ /dev/null @@ -1,492 +0,0 @@ -name: CI/CD Web + Desktop - -on: - push: - branches: [ main ] - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - force_web_deploy: - description: 'Forcar deploy do Web (ignorar filtro)?' - type: boolean - required: false - default: false - force_convex_deploy: - description: 'Forcar deploy do Convex (ignorar filtro)?' - type: boolean - required: false - default: false - -env: - APP_DIR: /srv/apps/sistema - VPS_UPDATES_DIR: /var/www/updates - -jobs: - changes: - name: Detect changes - runs-on: [ self-hosted, linux, vps ] - timeout-minutes: 5 - outputs: - convex: ${{ steps.filter.outputs.convex }} - web: ${{ steps.filter.outputs.web }} - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - name: Paths filter - id: filter - uses: https://github.com/dorny/paths-filter@v3 - with: - filters: | - convex: - - 'convex/**' - web: - - 'src/**' - - 'public/**' - - 'prisma/**' - - 'next.config.ts' - - 'package.json' - - 'bun.lock' - - 'tsconfig.json' - - 'middleware.ts' - - 'stack.yml' - - deploy: - name: Deploy (VPS Linux) - needs: changes - timeout-minutes: 30 - if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - FALLBACK_DIR="$HOME/apps/web.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Setup Bun - uses: https://github.com/oven-sh/setup-bun@v2 - with: - bun-version: 1.3.4 - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'" - if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then - EXCLUDE_ENV="" - fi - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --filter='protect .env' \ - --filter='protect .env*' \ - --filter='protect apps/desktop/.env*' \ - --filter='protect convex/.env*' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - $EXCLUDE_ENV \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Copy production .env if present - run: | - DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}" - if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then - echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR" - cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env" - fi - - - name: Ensure Next.js cache directory exists and is writable - run: | - cd "$EFFECTIVE_APP_DIR" - mkdir -p .next/cache - chmod -R u+rwX .next || true - - - name: Cache Next.js build cache (.next/cache) - uses: https://github.com/actions/cache@v4 - with: - path: ${{ env.EFFECTIVE_APP_DIR }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-${{ hashFiles('next.config.ts') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}- - ${{ runner.os }}-nextjs- - - - name: Lint check (fail fast before build) - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run lint" - - - name: Install and build (Next.js) - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -e PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING="$PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING" \ - -e NODE_OPTIONS="--max-old-space-size=4096" \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run prisma:generate; bun run build:bun" - - - name: Fix Docker-created file permissions - run: | - # Docker cria arquivos como root - corrigir para o usuario runner (UID 1000) - docker run --rm -v "$EFFECTIVE_APP_DIR":/target alpine:3 \ - chown -R 1000:1000 /target - echo "Permissoes do build corrigidas" - - - name: Atualizar symlink do APP_DIR estavel (deploy atomico) - run: | - set -euo pipefail - ROOT="$HOME/apps" - STABLE_LINK="$ROOT/sistema.current" - - mkdir -p "$ROOT" - - # Sanidade: se esses arquivos nao existirem, o container vai falhar no boot. - test -f "$EFFECTIVE_APP_DIR/scripts/start-web.sh" || { echo "ERROR: scripts/start-web.sh nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; } - test -f "$EFFECTIVE_APP_DIR/stack.yml" || { echo "ERROR: stack.yml nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; } - test -d "$EFFECTIVE_APP_DIR/node_modules" || { echo "ERROR: node_modules nao encontrado em $EFFECTIVE_APP_DIR (necessario para next start)" >&2; exit 1; } - test -d "$EFFECTIVE_APP_DIR/.next" || { echo "ERROR: .next nao encontrado em $EFFECTIVE_APP_DIR (build nao gerado)" >&2; exit 1; } - - PREV="" - if [ -L "$STABLE_LINK" ]; then - PREV="$(readlink -f "$STABLE_LINK" || true)" - fi - echo "PREV_APP_DIR=$PREV" >> "$GITHUB_ENV" - - ln -sfn "$EFFECTIVE_APP_DIR" "$STABLE_LINK" - - # Compat: mantem $HOME/apps/sistema como symlink quando possivel (nao mexe se for pasta). - if [ -L "$ROOT/sistema" ] || [ ! -e "$ROOT/sistema" ]; then - ln -sfn "$STABLE_LINK" "$ROOT/sistema" - fi - - echo "APP_DIR estavel -> $(readlink -f "$STABLE_LINK")" - - - name: Swarm deploy (stack.yml) - run: | - APP_DIR_STABLE="$HOME/apps/sistema.current" - if [ ! -d "$APP_DIR_STABLE" ]; then - echo "ERROR: Stable APP_DIR does not exist: $APP_DIR_STABLE" >&2; exit 1 - fi - cd "$APP_DIR_STABLE" - set -o allexport - if [ -f .env ]; then - echo "Loading .env from $APP_DIR_STABLE" - . ./.env - else - echo "WARNING: No .env found at $APP_DIR_STABLE - stack vars may be empty!" - fi - set +o allexport - echo "Using APP_DIR (stable)=$APP_DIR_STABLE" - echo "NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}" - echo "NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}" - APP_DIR="$APP_DIR_STABLE" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema - - - name: Wait for services to be healthy - run: | - echo "Aguardando servicos ficarem saudaveis..." - for i in $(seq 1 18); do - WEB_STATUS=$(docker service ls --filter "name=sistema_web" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - CONVEX_STATUS=$(docker service ls --filter "name=sistema_convex_backend" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - echo "Tentativa $i/18: web=$WEB_STATUS convex=$CONVEX_STATUS" - if echo "$WEB_STATUS" | grep -q "2/2" && echo "$CONVEX_STATUS" | grep -q "1/1"; then - echo "Todos os servicos estao saudaveis!" - exit 0 - fi - sleep 10 - done - echo "ERRO: Timeout aguardando servicos. Status atual:" - docker service ls --filter "label=com.docker.stack.namespace=sistema" || true - docker service ps sistema_web --no-trunc || true - docker service logs sistema_web --since 5m --raw 2>/dev/null | tail -n 200 || true - - if [ -n "${PREV_APP_DIR:-}" ]; then - echo "Rollback: revertendo APP_DIR estavel para: $PREV_APP_DIR" - ln -sfn "$PREV_APP_DIR" "$HOME/apps/sistema.current" - cd "$HOME/apps/sistema.current" - set -o allexport - if [ -f .env ]; then - . ./.env - fi - set +o allexport - APP_DIR="$HOME/apps/sistema.current" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema || true - fi - - exit 1 - - - name: Cleanup old build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='web.build.*' - ACTIVE="$(readlink -f "$HOME/apps/sistema.current" 2>/dev/null || true)" - echo "Scanning $ROOT for old $PATTERN dirs" - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - if [ -n "$ACTIVE" ] && [ "$(readlink -f "$dir")" = "$ACTIVE" ]; then - echo "Skipping active dir (in use by APP_DIR): $dir"; continue - fi - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - echo "Disk usage (top 10 under $ROOT):" - du -sh "$ROOT"/* 2>/dev/null | sort -rh | head -n 10 || true - - convex_deploy: - name: Deploy Convex functions - needs: changes - timeout-minutes: 20 - if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.convex == 'true' }} - runs-on: [ self-hosted, linux, vps ] - env: - APP_DIR: /srv/apps/sistema - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - FALLBACK_DIR="$HOME/apps/convex.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --exclude '.env*' \ - --exclude 'apps/desktop/.env*' \ - --exclude 'convex/.env*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Bring convex.json from live app if present - run: | - if [ -f "$APP_DIR/convex.json" ]; then - echo "Copying $APP_DIR/convex.json -> $EFFECTIVE_APP_DIR/convex.json" - cp -f "$APP_DIR/convex.json" "$EFFECTIVE_APP_DIR/convex.json" - else - echo "No existing convex.json found at $APP_DIR; convex CLI will need self-hosted vars" - fi - - - name: Set Convex env vars (self-hosted) - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }} - MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }} - FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }} - run: | - set -e - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e MACHINE_PROVISIONING_SECRET \ - -e MACHINE_TOKEN_TTL_MS \ - -e FLEET_SYNC_SECRET \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - - name: Prepare Convex deploy workspace - run: | - cd "$EFFECTIVE_APP_DIR" - if [ -f .env ]; then - echo "Renaming .env -> .env.bak (Convex self-hosted deploy)" - mv -f .env .env.bak - fi - mkdir -p .convex-tmp - - - name: Deploy functions to Convex self-hosted - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CI=true \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; bunx convex deploy" - - - name: Cleanup old convex build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='convex.build.*' - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - - # NOTA: Job comentado porque nao ha runner Windows configurado. - # Descomentar quando configurar um runner com labels: [self-hosted, windows, desktop] - # - # desktop_release: - # name: Desktop Release (Windows) - # timeout-minutes: 30 - # if: ${{ startsWith(github.ref, 'refs/tags/v') }} - # runs-on: [ self-hosted, windows, desktop ] - # defaults: - # run: - # working-directory: apps/desktop - # steps: - # - name: Checkout - # uses: https://github.com/actions/checkout@v4 - # - # - name: Setup pnpm - # uses: https://github.com/pnpm/action-setup@v4 - # with: - # version: 10.20.0 - # - # - name: Setup Node.js - # uses: https://github.com/actions/setup-node@v4 - # with: - # node-version: 20 - # - # - name: Install deps (desktop) - # run: pnpm install --frozen-lockfile - # - # - name: Build with Tauri - # uses: https://github.com/tauri-apps/tauri-action@v0 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - # TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - # with: - # projectPath: apps/desktop - # - # - name: Upload bundles to VPS - # run: | - # # Upload via SCP (configurar chave SSH no runner Windows) - # # scp -r src-tauri/target/release/bundle/* user@vps:/var/www/updates/ - # echo "TODO: Configurar upload para VPS" diff --git a/.forgejo/workflows/quality-checks.yml b/.forgejo/workflows/quality-checks.yml deleted file mode 100644 index daed18b..0000000 --- a/.forgejo/workflows/quality-checks.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Quality Checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint-test-build: - name: Lint, Test and Build - runs-on: [ self-hosted, linux, vps ] - env: - BETTER_AUTH_SECRET: test-secret - NEXT_PUBLIC_APP_URL: http://localhost:3000 - BETTER_AUTH_URL: http://localhost:3000 - NEXT_PUBLIC_CONVEX_URL: http://localhost:3210 - DATABASE_URL: file:./prisma/db.dev.sqlite - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Setup Bun - uses: https://github.com/oven-sh/setup-bun@v2 - with: - bun-version: 1.3.4 - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Cache Next.js build cache - uses: https://github.com/actions/cache@v4 - with: - path: | - ${{ github.workspace }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-${{ hashFiles('**/*.{js,jsx,ts,tsx}') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}- - - - name: Generate Prisma client - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: bun run prisma:generate - - - name: Lint - run: bun run lint - - - name: Test - run: bun test - - - name: Build - run: bun run build:bun diff --git a/.github/workflows.disabled/ci-cd-web-desktop.yml b/.github/workflows.disabled/ci-cd-web-desktop.yml deleted file mode 100644 index e95322c..0000000 --- a/.github/workflows.disabled/ci-cd-web-desktop.yml +++ /dev/null @@ -1,639 +0,0 @@ -name: CI/CD Web + Desktop - -on: - push: - branches: [ main ] - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - force_web_deploy: - description: 'Forçar deploy do Web (ignorar filtro)?' - required: false - default: 'false' - force_convex_deploy: - description: 'Forçar deploy do Convex (ignorar filtro)?' - required: false - default: 'false' - -env: - APP_DIR: /srv/apps/sistema - VPS_UPDATES_DIR: /var/www/updates - RUN_MACHINE_SMOKE: ${{ vars.RUN_MACHINE_SMOKE || secrets.RUN_MACHINE_SMOKE || 'false' }} - -jobs: - changes: - name: Detect changes - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - convex: ${{ steps.filter.outputs.convex }} - web: ${{ steps.filter.outputs.web }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Paths filter - id: filter - uses: dorny/paths-filter@v3 - with: - filters: | - convex: - - 'convex/**' - web: - - 'src/**' - - 'public/**' - - 'prisma/**' - - 'next.config.ts' - - 'package.json' - - 'pnpm-lock.yaml' - - 'tsconfig.json' - - 'middleware.ts' - - 'stack.yml' - - deploy: - name: Deploy (VPS Linux) - needs: changes - timeout-minutes: 30 - # Executa em qualquer push na main (independente do filtro) ou quando disparado manualmente - if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - # Use a web-specific build dir to avoid clashes with convex job - FALLBACK_DIR="$HOME/apps/web.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.20.0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.1 - - - name: Verify Bun runtime - run: bun --version - - - name: Permissions diagnostic (server paths) - run: | - set +e - echo "== Basic context ==" - whoami || true - id || true - groups || true - umask || true - echo "HOME=$HOME" - echo "APP_DIR(default)=${APP_DIR:-/srv/apps/sistema}" - echo "EFFECTIVE_APP_DIR=$EFFECTIVE_APP_DIR" - - echo "\n== Permissions check ==" - check_path() { - P="$1" - echo "-- $P" - if [ -e "$P" ]; then - stat -c '%A %U:%G %n' "$P" 2>/dev/null || ls -ld "$P" || true - echo -n "WRITABLE? "; [ -w "$P" ] && echo yes || echo no - if command -v namei >/dev/null 2>&1; then - namei -l "$P" || true - fi - TMP="$P/.permtest.$$" - (echo test > "$TMP" 2>/dev/null && echo "CREATE_FILE: ok" && rm -f "$TMP") || echo "CREATE_FILE: failed" - else - echo "(missing)" - fi - } - check_path "/srv/apps/sistema" - check_path "/srv/apps/sistema/src/app/machines/handshake" - check_path "/srv/apps/sistema/apps/desktop/node_modules" - check_path "/srv/apps/sistema/node_modules" - check_path "$EFFECTIVE_APP_DIR" - check_path "$EFFECTIVE_APP_DIR/node_modules" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - # Excluir .env apenas quando copiando para o diretório padrão (/srv) para preservar segredos locais - EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'" - if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then - EXCLUDE_ENV="" - fi - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --filter='protect .env' \ - --filter='protect .env*' \ - --filter='protect apps/desktop/.env*' \ - --filter='protect convex/.env*' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - $EXCLUDE_ENV \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - # Nao forca restart - deixa o Swarm gerenciar via health checks - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Copy production .env if present - run: | - DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}" - if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then - echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR" - cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env" - fi - - - name: Prune workspace for server-only build - run: | - cd "$EFFECTIVE_APP_DIR" - # Keep only root (web) as a package in this effective workspace - printf "packages:\n - .\n\nignoredBuiltDependencies:\n - '@prisma/client'\n - '@prisma/engines'\n - '@tailwindcss/oxide'\n - esbuild\n - prisma\n - sharp\n - unrs-resolver\n" > pnpm-workspace.yaml - - - name: Ensure Next.js cache directory exists and is writable - run: | - cd "$EFFECTIVE_APP_DIR" - mkdir -p .next/cache - chmod -R u+rwX .next || true - - - name: Cache Next.js build cache (.next/cache) - uses: actions/cache@v4 - with: - path: ${{ env.EFFECTIVE_APP_DIR }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}-${{ hashFiles('src/**/*.ts', 'src/**/*.tsx', 'src/**/*.js', 'src/**/*.jsx', 'next.config.ts') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}- - - - name: Lint check (fail fast before build) - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run lint" - - - name: Install and build (Next.js) - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -e PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING="$PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING" \ - -e NODE_OPTIONS="--max-old-space-size=4096" \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run prisma:generate; bun run build:bun" - - - name: Publish build to stable APP_DIR directory - run: | - set -e - DEST="$HOME/apps/sistema" - mkdir -p "$DEST" - mkdir -p "$DEST/.next/static" - # One-time fix for old root-owned files (esp. .pnpm-store) left by previous containers - docker run --rm -v "$DEST":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true' || true - # Preserve previously published static assets to keep stale chunks available for clients mid-navigation - if [ -d "$EFFECTIVE_APP_DIR/.next/static" ]; then - rsync -a \ - "$EFFECTIVE_APP_DIR/.next/static/" "$DEST/.next/static/" - fi - # Publish new build; exclude .pnpm-store to avoid Permission denied on old entries - rsync -a --delete \ - --chown=1000:1000 \ - --exclude '.pnpm-store' --exclude '.pnpm-store/**' \ - --exclude '.next/static' \ - "$EFFECTIVE_APP_DIR"/ "$DEST"/ - echo "Published build to: $DEST" - - - name: Swarm deploy (stack.yml) - run: | - APP_DIR_STABLE="$HOME/apps/sistema" - if [ ! -d "$APP_DIR_STABLE" ]; then - echo "ERROR: Stable APP_DIR does not exist: $APP_DIR_STABLE" >&2; exit 1 - fi - cd "$APP_DIR_STABLE" - # Exporta variáveis do .env (do diretório de produção) para substituição no stack - # IMPORTANTE: Usar o .env do APP_DIR_STABLE, não do EFFECTIVE_APP_DIR (build temporário) - set -o allexport - if [ -f .env ]; then - echo "Loading .env from $APP_DIR_STABLE" - . ./.env - else - echo "WARNING: No .env found at $APP_DIR_STABLE - stack vars may be empty!" - fi - set +o allexport - echo "Using APP_DIR (stable)=$APP_DIR_STABLE" - echo "NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}" - echo "NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}" - APP_DIR="$APP_DIR_STABLE" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema - - - name: Wait for services to be healthy - run: | - echo "Aguardando servicos ficarem saudaveis..." - # Aguarda ate 3 minutos (18 tentativas x 10s) pelos servicos - for i in $(seq 1 18); do - WEB_STATUS=$(docker service ls --filter "name=sistema_web" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - CONVEX_STATUS=$(docker service ls --filter "name=sistema_convex_backend" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - echo "Tentativa $i/18: web=$WEB_STATUS convex=$CONVEX_STATUS" - # Verifica se web tem 2/2 replicas e convex tem 1/1 - if echo "$WEB_STATUS" | grep -q "2/2" && echo "$CONVEX_STATUS" | grep -q "1/1"; then - echo "Todos os servicos estao saudaveis!" - exit 0 - fi - sleep 10 - done - echo "AVISO: Timeout aguardando servicos. Status atual:" - docker service ls --filter "label=com.docker.stack.namespace=sistema" - # Nao falha o deploy, apenas avisa (o Swarm continua o rolling update em background) - - - name: Smoke test — register + heartbeat - run: | - set -e - if [ "${RUN_MACHINE_SMOKE:-false}" != "true" ]; then - echo "RUN_MACHINE_SMOKE != true — pulando smoke test"; exit 0 - fi - # Load MACHINE_PROVISIONING_SECRET from production .env on the host - if [ -f /srv/apps/sistema/.env ]; then - set -o allexport - . /srv/apps/sistema/.env - set +o allexport - fi - if [ -z "${MACHINE_PROVISIONING_SECRET:-}" ]; then - echo "MACHINE_PROVISIONING_SECRET ausente — pulando smoke test"; exit 0 - fi - HOSTNAME_TEST="ci-smoke-$(date +%s)" - BODY='{"provisioningSecret":"'"$MACHINE_PROVISIONING_SECRET"'","tenantId":"tenant-atlas","hostname":"'"$HOSTNAME_TEST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventory":{"cpu":"i7","ramGb":16}},"registeredBy":"ci-smoke"}' - HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$BODY" https://tickets.esdrasrenan.com.br/api/machines/register || true) - echo "Register HTTP=$HTTP" - if [ "$HTTP" != "201" ]; then - echo "Register failed:"; tail -c 600 resp.json || true; exit 1; fi - TOKEN=$(node -e 'try{const j=require("fs").readFileSync("resp.json","utf8");process.stdout.write(JSON.parse(j).machineToken||"");}catch(e){process.stdout.write("")}' ) - if [ -z "$TOKEN" ]; then echo "Missing token in register response"; exit 1; fi - HB=$(curl -sS -o /dev/null -w "%{http_code}" -H 'Content-Type: application/json' -d '{"machineToken":"'"$TOKEN"'","status":"online","metrics":{"cpuPct":5,"memFreePct":70}}' https://tickets.esdrasrenan.com.br/api/machines/heartbeat || true) - echo "Heartbeat HTTP=$HB" - if [ "$HB" != "200" ]; then echo "Heartbeat failed"; exit 1; fi - - - name: Cleanup old build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='web.build.*' - ACTIVE="$HOME/apps/sistema" - echo "Scanning $ROOT for old $PATTERN dirs" - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - if [ -n "$ACTIVE" ] && [ "$(readlink -f "$dir")" = "$ACTIVE" ]; then - echo "Skipping active dir (in use by APP_DIR): $dir"; continue - fi - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - echo "Disk usage (top 10 under $ROOT):" - du -sh "$ROOT"/* 2>/dev/null | sort -rh | head -n 10 || true - - - name: Restart web service with new code (skip — stack deploy already updated) - if: ${{ always() && false }} - run: | - docker service update --force sistema_web - - # Comentado: o stack deploy já atualiza os serviços com update_config.order: start-first - # Forçar update aqui causa downtime porque ignora a estratégia de rolling update - # - name: Restart Convex backend service (optional) - # run: | - # docker service update --force sistema_convex_backend - - convex_deploy: - name: Deploy Convex functions - needs: changes - timeout-minutes: 20 - # Executa quando convex/** mudar ou via workflow_dispatch - if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.convex == 'true' }} - runs-on: [ self-hosted, linux, vps ] - env: - APP_DIR: /srv/apps/sistema - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - # Use a convex-specific build dir to avoid clashes with web job - FALLBACK_DIR="$HOME/apps/convex.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --exclude '.env*' \ - --exclude 'apps/desktop/.env*' \ - --exclude 'convex/.env*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - # Nao forca restart - deixa o Swarm gerenciar via health checks - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Bring convex.json from live app if present - run: | - if [ -f "$APP_DIR/convex.json" ]; then - echo "Copying $APP_DIR/convex.json -> $EFFECTIVE_APP_DIR/convex.json" - cp -f "$APP_DIR/convex.json" "$EFFECTIVE_APP_DIR/convex.json" - else - echo "No existing convex.json found at $APP_DIR; convex CLI will need self-hosted vars" - fi - - - name: Set Convex env vars (self-hosted) - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }} - MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }} - FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }} - run: | - set -e - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e MACHINE_PROVISIONING_SECRET \ - -e MACHINE_TOKEN_TTL_MS \ - -e FLEET_SYNC_SECRET \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - - name: Prepare Convex deploy workspace - run: | - cd "$EFFECTIVE_APP_DIR" - if [ -f .env ]; then - echo "Renaming .env -> .env.bak (Convex self-hosted deploy)" - mv -f .env .env.bak - fi - # Dedicated tmp dir outside convex/_generated so CLI cleanups don't remove it - mkdir -p .convex-tmp - - name: Deploy functions to Convex self-hosted - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CI=true \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; bunx convex deploy" - - - name: Cleanup old convex build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='convex.build.*' - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - - desktop_release: - name: Desktop Release (Windows) - timeout-minutes: 30 - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - runs-on: [ self-hosted, windows, desktop ] - defaults: - run: - working-directory: apps/desktop - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.20.0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install deps (desktop) - run: pnpm install --frozen-lockfile - - - name: Build with Tauri - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - with: - projectPath: apps/desktop - - - - name: Upload latest.json + bundles to VPS - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_USER }} - key: ${{ secrets.VPS_SSH_KEY }} - source: | - **/bundle/**/latest.json - **/bundle/**/* - target: ${{ env.VPS_UPDATES_DIR }} - overwrite: true - - diagnose_convex: - name: Diagnose Convex (env + register test) - timeout-minutes: 10 - if: ${{ github.event_name == 'workflow_dispatch' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Print service env and .env subset - run: | - echo "=== Convex service env ===" - docker service inspect sistema_convex_backend --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' || true - echo - echo "=== /srv/apps/sistema/.env subset ===" - [ -f /srv/apps/sistema/.env ] && grep -E '^(MACHINE_PROVISIONING_SECRET|MACHINE_TOKEN_TTL_MS|FLEET_SYNC_SECRET|NEXT_PUBLIC_CONVEX_URL)=' -n /srv/apps/sistema/.env || echo '(no .env)' - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - - name: List Convex env and set missing - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - set -e - if [ -f /srv/apps/sistema/.env ]; then - set -o allexport - . /srv/apps/sistema/.env - set +o allexport - fi - docker run --rm -i \ - -v /srv/apps/sistema:/app -w /app \ - -e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY="$ADMIN_KEY" \ - -e MACHINE_PROVISIONING_SECRET -e MACHINE_TOKEN_TTL_MS -e FLEET_SYNC_SECRET \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; bun install --frozen-lockfile; \ - unset CONVEX_DEPLOYMENT; bunx convex env list; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - name: Test register from runner - run: | - HOST="vm-teste-$(date +%s)" - DATA='{"provisioningSecret":"'"${MACHINE_PROVISIONING_SECRET:-"71daa9ef54cb224547e378f8121ca898b614446c142a132f73c2221b4d53d7d6"}"'","tenantId":"tenant-atlas","hostname":"'"$HOST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventario":{"cpu":"i7","ramGb":16}},"registeredBy":"diag-test"}' - HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$DATA" https://tickets.esdrasrenan.com.br/api/machines/register || true) - echo "Register HTTP=$HTTP" && tail -c 400 resp.json || true diff --git a/.github/workflows.disabled/desktop-release.yml b/.github/workflows.disabled/desktop-release.yml deleted file mode 100644 index d0b3c21..0000000 --- a/.github/workflows.disabled/desktop-release.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Desktop Release (Tauri) - -on: - workflow_dispatch: - push: - tags: - - 'desktop-v*' - -permissions: - contents: write - -jobs: - build: - name: Build ${{ matrix.platform }} - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - include: - - platform: linux - runner: ubuntu-latest - - platform: windows - runner: windows-latest - - platform: macos - runner: macos-latest - defaults: - run: - shell: bash - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Enable Corepack - run: corepack enable && corepack prepare pnpm@10.20.0 --activate - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - - name: Install Linux deps - if: matrix.platform == 'linux' - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev libssl-dev build-essential curl wget file - - - name: Install pnpm deps - run: pnpm -C apps/desktop install --frozen-lockfile - - - - name: Build desktop - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - VITE_APP_URL: https://tickets.esdrasrenan.com.br - VITE_API_BASE_URL: https://tickets.esdrasrenan.com.br - run: pnpm -C apps/desktop tauri build - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: desktop-${{ matrix.platform }} - path: apps/desktop/src-tauri/target/release/bundle diff --git a/.github/workflows.disabled/quality-checks.yml b/.github/workflows.disabled/quality-checks.yml deleted file mode 100644 index a14a2d7..0000000 --- a/.github/workflows.disabled/quality-checks.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Quality Checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint-test-build: - name: Lint, Test and Build - runs-on: ubuntu-latest - env: - BETTER_AUTH_SECRET: test-secret - NEXT_PUBLIC_APP_URL: http://localhost:3000 - BETTER_AUTH_URL: http://localhost:3000 - NEXT_PUBLIC_CONVEX_URL: http://localhost:3210 - DATABASE_URL: file:./prisma/db.dev.sqlite - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.1 - - - name: Verify Bun - run: bun --version - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Cache Next.js build cache - uses: actions/cache@v4 - with: - path: | - ${{ github.workspace }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}-${{ hashFiles('**/*.{js,jsx,ts,tsx}') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}- - - - name: Generate Prisma client - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: bun run prisma:generate - - - name: Lint - run: bun run lint - - - name: Test - run: bun test - - - name: Build - run: bun run build:bun diff --git a/.gitignore b/.gitignore index 30d6e0c..f3b0871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,73 +1,9 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# React Email -/.react-email/ -/emails/out/ - -# production -/build - -# misc -.DS_Store -*.pem -*.sqlite -# external experiments -nova-calendar-main/ - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* -!.env.example -!apps/desktop/.env.example - -# Accidental Windows duplicate downloads (e.g., "env (1)") -env (*) -env (1) - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# backups locais -.archive/ - -# arquivos locais temporários -Captura de tela *.png -Screenshot*.png -# Ignore NTFS ADS streams accidentally committed from Windows downloads -*:*Zone.Identifier -*:\:Zone.Identifier -# Infrastructure secrets -.ci.env - -# ferramentas externas -rustdesk/ - -# Prisma generated files -src/generated/ -apps/desktop/service/target/ +# Root ignore for monorepo +web/node_modules/ +web/.next/ +web/.turbo/ +web/out/ +web/.env.local +web/.env* +.DS_Store +Thumbs.db diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index bb79ec4..0000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,29 +0,0 @@ -# Runtime image with Node 22 + Bun 1.3.4 and build toolchain preinstalled -FROM node:22-bullseye-slim - -ENV BUN_INSTALL=/root/.bun -ENV PATH="$BUN_INSTALL/bin:$PATH" - -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - gnupg \ - unzip \ - build-essential \ - python3 \ - make \ - pkg-config \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Install Bun 1.3.4 -RUN curl -fsSL https://bun.sh/install \ - | bash -s -- bun-v1.3.4 \ - && ln -sf /root/.bun/bin/bun /usr/local/bin/bun \ - && ln -sf /root/.bun/bin/bun /usr/local/bin/bunx - -WORKDIR /app - -# We'll mount the app code at runtime; image just provides runtimes/toolchains. -CMD ["bash"] diff --git a/Inter,Manrope/Inter/Inter-Italic-VariableFont_opsz,wght.ttf b/Inter,Manrope/Inter/Inter-Italic-VariableFont_opsz,wght.ttf deleted file mode 100644 index 43ed4f5..0000000 Binary files a/Inter,Manrope/Inter/Inter-Italic-VariableFont_opsz,wght.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/Inter-VariableFont_opsz,wght.ttf b/Inter,Manrope/Inter/Inter-VariableFont_opsz,wght.ttf deleted file mode 100644 index e31b51e..0000000 Binary files a/Inter,Manrope/Inter/Inter-VariableFont_opsz,wght.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/OFL.txt b/Inter,Manrope/Inter/OFL.txt deleted file mode 100644 index d05ec4b..0000000 --- a/Inter,Manrope/Inter/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://openfontlicense.org - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Inter,Manrope/Inter/README.txt b/Inter,Manrope/Inter/README.txt deleted file mode 100644 index b92a417..0000000 --- a/Inter,Manrope/Inter/README.txt +++ /dev/null @@ -1,118 +0,0 @@ -Inter Variable Font -=================== - -This download contains Inter as both variable fonts and static fonts. - -Inter is a variable font with these axes: - opsz - wght - -This means all the styles are contained in these files: - Inter/Inter-VariableFont_opsz,wght.ttf - Inter/Inter-Italic-VariableFont_opsz,wght.ttf - -If your app fully supports variable fonts, you can now pick intermediate styles -that aren’t available as static fonts. Not all apps support variable fonts, and -in those cases you can use the static font files for Inter: - Inter/static/Inter_18pt-Thin.ttf - Inter/static/Inter_18pt-ExtraLight.ttf - Inter/static/Inter_18pt-Light.ttf - Inter/static/Inter_18pt-Regular.ttf - Inter/static/Inter_18pt-Medium.ttf - Inter/static/Inter_18pt-SemiBold.ttf - Inter/static/Inter_18pt-Bold.ttf - Inter/static/Inter_18pt-ExtraBold.ttf - Inter/static/Inter_18pt-Black.ttf - Inter/static/Inter_24pt-Thin.ttf - Inter/static/Inter_24pt-ExtraLight.ttf - Inter/static/Inter_24pt-Light.ttf - Inter/static/Inter_24pt-Regular.ttf - Inter/static/Inter_24pt-Medium.ttf - Inter/static/Inter_24pt-SemiBold.ttf - Inter/static/Inter_24pt-Bold.ttf - Inter/static/Inter_24pt-ExtraBold.ttf - Inter/static/Inter_24pt-Black.ttf - Inter/static/Inter_28pt-Thin.ttf - Inter/static/Inter_28pt-ExtraLight.ttf - Inter/static/Inter_28pt-Light.ttf - Inter/static/Inter_28pt-Regular.ttf - Inter/static/Inter_28pt-Medium.ttf - Inter/static/Inter_28pt-SemiBold.ttf - Inter/static/Inter_28pt-Bold.ttf - Inter/static/Inter_28pt-ExtraBold.ttf - Inter/static/Inter_28pt-Black.ttf - Inter/static/Inter_18pt-ThinItalic.ttf - Inter/static/Inter_18pt-ExtraLightItalic.ttf - Inter/static/Inter_18pt-LightItalic.ttf - Inter/static/Inter_18pt-Italic.ttf - Inter/static/Inter_18pt-MediumItalic.ttf - Inter/static/Inter_18pt-SemiBoldItalic.ttf - Inter/static/Inter_18pt-BoldItalic.ttf - Inter/static/Inter_18pt-ExtraBoldItalic.ttf - Inter/static/Inter_18pt-BlackItalic.ttf - Inter/static/Inter_24pt-ThinItalic.ttf - Inter/static/Inter_24pt-ExtraLightItalic.ttf - Inter/static/Inter_24pt-LightItalic.ttf - Inter/static/Inter_24pt-Italic.ttf - Inter/static/Inter_24pt-MediumItalic.ttf - Inter/static/Inter_24pt-SemiBoldItalic.ttf - Inter/static/Inter_24pt-BoldItalic.ttf - Inter/static/Inter_24pt-ExtraBoldItalic.ttf - Inter/static/Inter_24pt-BlackItalic.ttf - Inter/static/Inter_28pt-ThinItalic.ttf - Inter/static/Inter_28pt-ExtraLightItalic.ttf - Inter/static/Inter_28pt-LightItalic.ttf - Inter/static/Inter_28pt-Italic.ttf - Inter/static/Inter_28pt-MediumItalic.ttf - Inter/static/Inter_28pt-SemiBoldItalic.ttf - Inter/static/Inter_28pt-BoldItalic.ttf - Inter/static/Inter_28pt-ExtraBoldItalic.ttf - Inter/static/Inter_28pt-BlackItalic.ttf - -Get started ------------ - -1. Install the font files you want to use - -2. Use your app's font picker to view the font family and all the -available styles - -Learn more about variable fonts -------------------------------- - - https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts - https://variablefonts.typenetwork.com - https://medium.com/variable-fonts - -In desktop apps - - https://theblog.adobe.com/can-variable-fonts-illustrator-cc - https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts - -Online - - https://developers.google.com/fonts/docs/getting_started - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide - https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts - -Installing fonts - - MacOS: https://support.apple.com/en-us/HT201749 - Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux - Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows - -Android Apps - - https://developers.google.com/fonts/docs/android - https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts - -License -------- -Please read the full license text (OFL.txt) to understand the permissions, -restrictions and requirements for usage, redistribution, and modification. - -You can use them in your products & projects – print or digital, -commercial or otherwise. - -This isn't legal advice, please consider consulting a lawyer and see the full -license for all details. diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Black.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Black.ttf deleted file mode 100644 index 89673de..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Black.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-BlackItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-BlackItalic.ttf deleted file mode 100644 index b33602f..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-BlackItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Bold.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Bold.ttf deleted file mode 100644 index 57704d1..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Bold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-BoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-BoldItalic.ttf deleted file mode 100644 index d53a199..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-BoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-ExtraBold.ttf b/Inter,Manrope/Inter/static/Inter_18pt-ExtraBold.ttf deleted file mode 100644 index e71c601..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-ExtraBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-ExtraBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-ExtraBoldItalic.ttf deleted file mode 100644 index df45062..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-ExtraBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-ExtraLight.ttf b/Inter,Manrope/Inter/static/Inter_18pt-ExtraLight.ttf deleted file mode 100644 index f9c6cfc..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-ExtraLight.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-ExtraLightItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-ExtraLightItalic.ttf deleted file mode 100644 index 275f305..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-ExtraLightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Italic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Italic.ttf deleted file mode 100644 index 14d3595..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Italic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Light.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Light.ttf deleted file mode 100644 index acae361..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Light.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-LightItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-LightItalic.ttf deleted file mode 100644 index f69e18b..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-LightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Medium.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Medium.ttf deleted file mode 100644 index 71d9017..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Medium.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-MediumItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-MediumItalic.ttf deleted file mode 100644 index 5c8c8b1..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-MediumItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Regular.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Regular.ttf deleted file mode 100644 index ce097c8..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Regular.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-SemiBold.ttf b/Inter,Manrope/Inter/static/Inter_18pt-SemiBold.ttf deleted file mode 100644 index 053185e..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-SemiBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-SemiBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-SemiBoldItalic.ttf deleted file mode 100644 index d9c9896..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-SemiBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-Thin.ttf b/Inter,Manrope/Inter/static/Inter_18pt-Thin.ttf deleted file mode 100644 index e68ec47..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-Thin.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_18pt-ThinItalic.ttf b/Inter,Manrope/Inter/static/Inter_18pt-ThinItalic.ttf deleted file mode 100644 index 134e837..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_18pt-ThinItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Black.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Black.ttf deleted file mode 100644 index dbb1b3b..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Black.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-BlackItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-BlackItalic.ttf deleted file mode 100644 index b89d61c..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-BlackItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Bold.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Bold.ttf deleted file mode 100644 index e974d96..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Bold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-BoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-BoldItalic.ttf deleted file mode 100644 index 1c3d251..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-BoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-ExtraBold.ttf b/Inter,Manrope/Inter/static/Inter_24pt-ExtraBold.ttf deleted file mode 100644 index b775c08..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-ExtraBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-ExtraBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-ExtraBoldItalic.ttf deleted file mode 100644 index 3461a92..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-ExtraBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-ExtraLight.ttf b/Inter,Manrope/Inter/static/Inter_24pt-ExtraLight.ttf deleted file mode 100644 index 2ec6ca3..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-ExtraLight.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-ExtraLightItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-ExtraLightItalic.ttf deleted file mode 100644 index c634a5d..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-ExtraLightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Italic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Italic.ttf deleted file mode 100644 index 1048b07..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Italic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Light.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Light.ttf deleted file mode 100644 index 1a2a6f2..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Light.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-LightItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-LightItalic.ttf deleted file mode 100644 index ded5a75..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-LightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Medium.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Medium.ttf deleted file mode 100644 index 5c88739..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Medium.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-MediumItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-MediumItalic.ttf deleted file mode 100644 index be091b1..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-MediumItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Regular.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Regular.ttf deleted file mode 100644 index 6b088a7..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Regular.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-SemiBold.ttf b/Inter,Manrope/Inter/static/Inter_24pt-SemiBold.ttf deleted file mode 100644 index ceb8576..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-SemiBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-SemiBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-SemiBoldItalic.ttf deleted file mode 100644 index 6921df2..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-SemiBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-Thin.ttf b/Inter,Manrope/Inter/static/Inter_24pt-Thin.ttf deleted file mode 100644 index 3505b35..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-Thin.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_24pt-ThinItalic.ttf b/Inter,Manrope/Inter/static/Inter_24pt-ThinItalic.ttf deleted file mode 100644 index a3e6feb..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_24pt-ThinItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Black.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Black.ttf deleted file mode 100644 index 66a252f..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Black.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-BlackItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-BlackItalic.ttf deleted file mode 100644 index 3c8fdf9..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-BlackItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Bold.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Bold.ttf deleted file mode 100644 index 14db994..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Bold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-BoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-BoldItalic.ttf deleted file mode 100644 index 704b12b..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-BoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-ExtraBold.ttf b/Inter,Manrope/Inter/static/Inter_28pt-ExtraBold.ttf deleted file mode 100644 index 6d87cae..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-ExtraBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-ExtraBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-ExtraBoldItalic.ttf deleted file mode 100644 index 1a56735..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-ExtraBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-ExtraLight.ttf b/Inter,Manrope/Inter/static/Inter_28pt-ExtraLight.ttf deleted file mode 100644 index d42b3f5..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-ExtraLight.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-ExtraLightItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-ExtraLightItalic.ttf deleted file mode 100644 index 90e2f20..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-ExtraLightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Italic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Italic.ttf deleted file mode 100644 index c2a143a..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Italic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Light.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Light.ttf deleted file mode 100644 index 5eeff3a..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Light.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-LightItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-LightItalic.ttf deleted file mode 100644 index 6b90b76..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-LightItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Medium.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Medium.ttf deleted file mode 100644 index 00120fe..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Medium.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-MediumItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-MediumItalic.ttf deleted file mode 100644 index 7481e7b..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-MediumItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Regular.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Regular.ttf deleted file mode 100644 index 855b6f4..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Regular.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-SemiBold.ttf b/Inter,Manrope/Inter/static/Inter_28pt-SemiBold.ttf deleted file mode 100644 index 8b84efc..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-SemiBold.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-SemiBoldItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-SemiBoldItalic.ttf deleted file mode 100644 index 2e22c5a..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-SemiBoldItalic.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-Thin.ttf b/Inter,Manrope/Inter/static/Inter_28pt-Thin.ttf deleted file mode 100644 index 94e6108..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-Thin.ttf and /dev/null differ diff --git a/Inter,Manrope/Inter/static/Inter_28pt-ThinItalic.ttf b/Inter,Manrope/Inter/static/Inter_28pt-ThinItalic.ttf deleted file mode 100644 index d3d44cd..0000000 Binary files a/Inter,Manrope/Inter/static/Inter_28pt-ThinItalic.ttf and /dev/null differ diff --git a/README.md b/README.md deleted file mode 100644 index 47cd254..0000000 --- a/README.md +++ /dev/null @@ -1,131 +0,0 @@ -## Sistema de Chamados - -Aplicacao **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better Auth** para gestao de tickets da Rever. A stack ainda inclui **Prisma 7** (PostgreSQL), **Tailwind** e **Turbopack** como bundler padrao (webpack permanece disponivel como fallback). Todo o codigo-fonte fica na raiz do monorepo seguindo as convencoes do App Router. - -## Requisitos - -- Bun >= 1.3 (recomendado 1.3.1). Após instalar via script oficial, adicione `export PATH="$HOME/.bun/bin:$PATH"` ao seu shell (ex.: `.bashrc`) para ter `bun` disponível globalmente. -- Node.js >= 20 (necessário para ferramentas auxiliares como Prisma CLI e Next.js em modo fallback). -- CLI do Convex (`bunx convex dev` instalará automaticamente no primeiro uso, se ainda não estiver presente). -- GitHub Actions/autodeploy dependem dessas versões e do CLI do Convex disponível; use `npx convex --help` para confirmar. - -## Configuração rápida - -1. Instale as dependências: - ```bash - bun install - ``` -2. Ajuste o arquivo `.env` (ou crie a partir de `.env.example`) e confirme os valores de: - - `NEXT_PUBLIC_CONVEX_URL` (gerado pelo Convex Dev) - - `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `DATABASE_URL` (PostgreSQL, ex: `postgresql://postgres:dev@localhost:5432/sistema_chamados`) -3. Aplique as migrações e gere o client Prisma: - ```bash - bunx prisma migrate deploy - bun run prisma:generate - ``` -4. Popule usuários padrão do Better Auth: - ```bash - bun run auth:seed - ``` - > Sempre que trocar de máquina ou quiser “zerar” o ambiente local, basta repetir os passos 3 e 4 com a mesma `DATABASE_URL`. - -### Resetar rapidamente o ambiente local - -1. Suba um PostgreSQL local (Docker recomendado): - ```bash - docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18 - ``` -2. Aplique as migracoes: - ```bash - bunx prisma migrate deploy - ``` -3. Recrie/garanta as contas padrao de login: - ```bash - bun run auth:seed - ``` -4. Suba o servidor normalmente com `bun run dev`. - -### Subir serviços locais - -- (Opcional) Para re-sincronizar manualmente as filas padrão, execute `bun run queues:ensure`. -- Em um terminal, rode o backend em tempo real do Convex com `bun run convex:dev:bun` (ou `bun run convex:dev` para o runtime Node). -- Em outro terminal, suba o frontend Next.js (Turbopack) com `bun run dev:bun` (`bun run dev:webpack` serve como fallback). -- Com o Convex rodando, acesse `http://localhost:3000/dev/seed` uma vez para popular dados de demonstração (tickets, usuários, comentários). - -> Se o CLI perguntar sobre configuração do projeto Convex, escolha criar um novo deployment local (opção padrão) e confirme. As credenciais são armazenadas em `.convex/` automaticamente. - -### Documentação -- Índice de docs: `docs/README.md` -- Operações (produção): `docs/OPERATIONS.md` (versão EN) e `docs/OPERACAO-PRODUCAO.md` (PT-BR) -- Guia de DEV: `docs/DEV.md` -- Testes automatizados (Vitest/Playwright): `docs/testes-vitest.md` -- Stack Swarm: `stack.yml` (roteado por Traefik, rede `traefik_public`). - -### Variáveis de ambiente - -- Exemplo na raiz: `.env.example` — copie para `.env` e preencha segredos. -- App Desktop: `apps/desktop/.env.example` — copie para `apps/desktop/.env` e ajuste `VITE_APP_URL`. -- Nunca faça commit de arquivos `.env` com valores reais (já ignorados em `.gitignore`). - -### Guia de DEV (Prisma, Auth e Desktop/Tauri) - -Para fluxos detalhados de desenvolvimento — banco de dados local (PostgreSQL/Prisma), seed do Better Auth, ajustes do Prisma CLI no DEV e build do Desktop (Tauri) — consulte `docs/DEV.md`. - -## Scripts úteis - -- `bun run dev:bun` — padrão atual para o Next.js com runtime Bun (`bun run dev:webpack` permanece como fallback). -- `bun run convex:dev:bun` — runtime Bun para o Convex (`bun run convex:dev` mantém o fluxo antigo usando Node). -- `bun run build:bun` / `bun run start:bun` — build e serve com Bun usando Turbopack (padrão atual). -- `bun run dev:webpack` — fallback do Next.js em modo desenvolvimento (webpack). -- `bun run lint` — ESLint com as regras do projeto. -- `bun test` — suíte de testes unitários usando o runner do Bun (o teste de screenshot fica automaticamente ignorado se o matcher não existir). -- `bun run build` — executa `next build --turbopack` (runtime Node, caso prefira evitar o `--bun`). -- `bun run build:webpack` — executa `next build --webpack` como fallback oficial. -- `bun run auth:seed` — atualiza/cria contas padrao do Better Auth (credenciais em `agents.md`). -- `bunx prisma migrate deploy` — aplica migracoes ao banco PostgreSQL. -- `bun run convex:dev` — roda o Convex em modo desenvolvimento com Node, gerando tipos em `convex/_generated`. - -## Transferir dispositivo entre colaboradores - -Quando uma dispositivo trocar de responsável: - -1. Abra `Admin > Dispositivos`, selecione o equipamento e clique em **Resetar agente**. -2. No equipamento, execute o reset local do agente (`rever-agent reset` ou reinstale o serviço) e reprovisione com o código da empresa. -3. Após o agente gerar um novo token, associe a dispositivo ao novo colaborador no painel. - -Sem o reset de agente, o Convex reaproveita o token anterior e o inventário continua vinculado ao usuário antigo. - -## Estrutura principal - -- `app/` dentro de `src/` — rotas e layouts do Next.js (App Router). -- `components/` — componentes reutilizáveis (UI, formulários, layouts). -- `convex/` — queries, mutations e seeds do Convex. -- `prisma/` — schema e migracoes do Prisma (PostgreSQL). -- `scripts/` — utilitários em Node para sincronização e seeds adicionais. -- `agents.md` — guia operacional e contexto funcional (em PT-BR). -- `PROXIMOS_PASSOS.md` — backlog de melhorias futuras. - -## Credenciais de demonstração - -Após executar `bun run auth:seed`, as credenciais padrão ficam disponíveis conforme descrito em `agents.md` (seção “Credenciais padrão”). Ajuste variáveis `SEED_USER_*` se precisar sobrepor usuários ou senhas durante o seed. - -## Próximos passos - -Consulte `PROXIMOS_PASSOS.md` para acompanhar o backlog funcional e o progresso das iniciativas planejadas. - -### Executar com Bun - -- `bun install` é o fluxo padrão (o arquivo `bun.lock` deve ser versionado; use `bun install --frozen-lockfile` em CI). -- `bun run dev:bun`, `bun run convex:dev:bun`, `bun run build:bun` e `bun run start:bun` já estão configurados; internamente executam `bun run --bun - - -
- - diff --git a/apps/desktop/package.json b/apps/desktop/package.json deleted file mode 100644 index 1c403b7..0000000 --- a/apps/desktop/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "appsdesktop", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "tauri": "node ./scripts/tauri-with-stub.mjs", - "gen:icon": "node ./scripts/build-icon.mjs", - "build:service": "cd service && cargo build --release", - "build:all": "bun run build:service && bun run tauri build" - }, - "dependencies": { - "@radix-ui/react-scroll-area": "^1.2.3", - "@radix-ui/react-tabs": "^1.1.13", - "@tauri-apps/api": "^2.9.1", - "@tauri-apps/plugin-dialog": "^2.4.2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-process": "^2", - "@tauri-apps/plugin-store": "^2", - "@tauri-apps/plugin-updater": "^2", - "convex": "^1.31.0", - "lucide-react": "^0.544.0", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@tauri-apps/cli": "^2", - "@vitejs/plugin-react": "^4.3.4", - "baseline-browser-mapping": "^2.9.2", - "png-to-ico": "^3.0.1", - "typescript": "~5.6.2", - "vite": "^6.0.3" - } -} diff --git a/apps/desktop/public/latest.json b/apps/desktop/public/latest.json deleted file mode 100644 index 8faf2f3..0000000 --- a/apps/desktop/public/latest.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.1.6", - "notes": "Correções e melhorias do desktop", - "pub_date": "2025-10-14T12:00:00Z", - "platforms": { - "windows-x86_64": { - "signature": "ZFc1MGNuVnpkR1ZrSUdOdmJXMWxiblE2SUhOcFoyNWhkSFZ5WlNCbWNtOXRJSFJoZFhKcElITmxZM0psZENCclpYa0tVbFZVZDNFeFUwRlJRalJVUjJOU1NqUnpTVmhXU1ZoeVUwZElNSGxETW5KSE1FTnBWa3BWU1dzelVYVlRNV1JTV0Vrdk1XMUZVa0Z3YTBWc2QySnZhVnBxUWs5bVoyODNNbEZaYUZsMFVHTlRLMUFyT0hJMVdGZ3lWRkZYT1V3ekwzZG5QUXAwY25WemRHVmtJR052YlcxbGJuUTZJSFJwYldWemRHRnRjRG94TnpZd016azVOVEkzQ1dacGJHVTZVbUYyWlc1Zk1DNHhMalZmZURZMExYTmxkSFZ3TG1WNFpRcHdkME15THpOVlZtUXpiSG9yZGpRd1pFZHFhV1JvVkZCb0wzVnNabWh1ZURJdmFtUlZOalEwTkRSVVdVY3JUVGhLTUdrNU5scFNUSFZVWkRsc1lYVTJUR2dyWTNWeWJuWTVhRGh3ZVVnM1dFWjVhSFZDUVQwOUNnPT0=", - "url": "https://github.com/esdrasrenan/sistema-de-chamados/raw/main/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe" - } - } -} diff --git a/apps/desktop/public/logo-raven.png b/apps/desktop/public/logo-raven.png deleted file mode 100644 index 62b264e..0000000 Binary files a/apps/desktop/public/logo-raven.png and /dev/null differ diff --git a/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe b/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe deleted file mode 100644 index 2d38474..0000000 Binary files a/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe and /dev/null differ diff --git a/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe.sig b/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe.sig deleted file mode 100644 index 3ce4efb..0000000 --- a/apps/desktop/public/releases/Raven_0.1.6_x64-setup.exe.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUd3ExU0FRQjRUR2NSSjRzSVhWSVhyU0dIMHlDMnJHMENpVkpVSWszUXVTMWRSWEkvMW1FUkFwa0Vsd2JvaVpqQk9mZ283MlFZaFl0UGNTK1ArOHI1WFgyVFFXOUwzL3dnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzYwMzk5NTI3CWZpbGU6UmF2ZW5fMC4xLjVfeDY0LXNldHVwLmV4ZQpwd0MyLzNVVmQzbHordjQwZEdqaWRoVFBoL3VsZmhueDIvamRVNjQ0NDRUWUcrTThKMGk5NlpSTHVUZDlsYXU2TGgrY3VybnY5aDhweUg3WEZ5aHVCQT09Cg== \ No newline at end of file diff --git a/apps/desktop/scripts/build-icon.mjs b/apps/desktop/scripts/build-icon.mjs deleted file mode 100644 index ffb3649..0000000 --- a/apps/desktop/scripts/build-icon.mjs +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node -import { promises as fs } from 'node:fs' -import path from 'node:path' -import pngToIco from 'png-to-ico' - -async function fileExists(p) { - try { await fs.access(p); return true } catch { return false } -} - -async function main() { - const root = path.resolve(process.cwd(), 'src-tauri', 'icons') - // Inclua apenas tamanhos suportados pelo NSIS (até 256px). - // Evite 512px para não gerar ICO inválido para o instalador. - const candidates = [ - 'icon-256.png', // preferencial - '128x128@2x.png', // alias de 256 - 'icon-128.png', - 'icon-64.png', - 'icon-32.png', - ] - const sources = [] - for (const name of candidates) { - const p = path.join(root, name) - if (await fileExists(p)) sources.push(p) - } - if (sources.length === 0) { - console.error('[gen:icon] Nenhuma imagem base encontrada em src-tauri/icons') - process.exit(1) - } - - console.log('[gen:icon] Gerando icon.ico a partir de:', sources.map((s) => path.basename(s)).join(', ')) - const buffer = await pngToIco(sources) - const outPath = path.join(root, 'icon.ico') - await fs.writeFile(outPath, buffer) - console.log('[gen:icon] Escrito:', outPath) -} - -main().catch((err) => { console.error(err); process.exit(1) }) diff --git a/apps/desktop/scripts/generate_icon_assets.py b/apps/desktop/scripts/generate_icon_assets.py deleted file mode 100644 index 73b0eec..0000000 --- a/apps/desktop/scripts/generate_icon_assets.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate icon PNGs/ICO for the desktop installer using the high-resolution Raven artwork. - -The script reads the square logo (`logo-raven-fund-azul.png`) and resizes it to the -target sizes with a simple bilinear filter implemented with the Python standard library, -avoiding additional dependencies. -""" - -from __future__ import annotations - -import math -import struct -import zlib -from binascii import crc32 -from pathlib import Path - -ICON_DIR = Path(__file__).resolve().parents[1] / "src-tauri" / "icons" -BASE_IMAGE = ICON_DIR / "logo-raven-fund-azul.png" -TARGET_SIZES = [32, 64, 128, 256, 512] - - -def read_png(path: Path) -> tuple[int, int, list[list[tuple[int, int, int, int]]]]: - data = path.read_bytes() - if not data.startswith(b"\x89PNG\r\n\x1a\n"): - raise ValueError(f"{path} is not a PNG") - pos = 8 - width = height = bit_depth = color_type = None - compressed_parts = [] - while pos < len(data): - length = struct.unpack(">I", data[pos : pos + 4])[0] - pos += 4 - ctype = data[pos : pos + 4] - pos += 4 - chunk = data[pos : pos + length] - pos += length - pos += 4 # CRC - if ctype == b"IHDR": - width, height, bit_depth, color_type, _, _, _ = struct.unpack(">IIBBBBB", chunk) - if bit_depth != 8 or color_type not in (2, 6): - raise ValueError("Only 8-bit RGB/RGBA PNGs are supported") - elif ctype == b"IDAT": - compressed_parts.append(chunk) - elif ctype == b"IEND": - break - if width is None or height is None or bit_depth is None or color_type is None: - raise ValueError("PNG missing IHDR chunk") - - raw = zlib.decompress(b"".join(compressed_parts)) - bpp = 4 if color_type == 6 else 3 - stride = width * bpp - rows = [] - idx = 0 - prev = bytearray(stride) - for _ in range(height): - filter_type = raw[idx] - idx += 1 - row = bytearray(raw[idx : idx + stride]) - idx += stride - if filter_type == 1: - for i in range(stride): - left = row[i - bpp] if i >= bpp else 0 - row[i] = (row[i] + left) & 0xFF - elif filter_type == 2: - for i in range(stride): - row[i] = (row[i] + prev[i]) & 0xFF - elif filter_type == 3: - for i in range(stride): - left = row[i - bpp] if i >= bpp else 0 - up = prev[i] - row[i] = (row[i] + ((left + up) // 2)) & 0xFF - elif filter_type == 4: - for i in range(stride): - left = row[i - bpp] if i >= bpp else 0 - up = prev[i] - up_left = prev[i - bpp] if i >= bpp else 0 - p = left + up - up_left - pa = abs(p - left) - pb = abs(p - up) - pc = abs(p - up_left) - if pa <= pb and pa <= pc: - pr = left - elif pb <= pc: - pr = up - else: - pr = up_left - row[i] = (row[i] + pr) & 0xFF - elif filter_type not in (0,): - raise ValueError(f"Unsupported PNG filter type {filter_type}") - rows.append(bytes(row)) - prev[:] = row - - pixels: list[list[tuple[int, int, int, int]]] = [] - for row in rows: - if color_type == 6: - pixels.append([tuple(row[i : i + 4]) for i in range(0, len(row), 4)]) - else: - pixels.append([tuple(row[i : i + 3] + b"\xff") for i in range(0, len(row), 3)]) - return width, height, pixels - - -def write_png(path: Path, width: int, height: int, pixels: list[list[tuple[int, int, int, int]]]) -> None: - raw = bytearray() - for row in pixels: - raw.append(0) # filter type 0 - for r, g, b, a in row: - raw.extend((r & 0xFF, g & 0xFF, b & 0xFF, a & 0xFF)) - compressed = zlib.compress(raw, level=9) - - def chunk(name: bytes, payload: bytes) -> bytes: - return ( - struct.pack(">I", len(payload)) - + name - + payload - + struct.pack(">I", crc32(name + payload) & 0xFFFFFFFF) - ) - - ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0) - out = bytearray(b"\x89PNG\r\n\x1a\n") - out += chunk(b"IHDR", ihdr) - out += chunk(b"IDAT", compressed) - out += chunk(b"IEND", b"") - path.write_bytes(out) - - -def bilinear_sample(pixels: list[list[tuple[int, int, int, int]]], x: float, y: float) -> tuple[int, int, int, int]: - height = len(pixels) - width = len(pixels[0]) - x = min(max(x, 0.0), width - 1.0) - y = min(max(y, 0.0), height - 1.0) - x0 = int(math.floor(x)) - y0 = int(math.floor(y)) - x1 = min(x0 + 1, width - 1) - y1 = min(y0 + 1, height - 1) - dx = x - x0 - dy = y - y0 - - def lerp(a: float, b: float, t: float) -> float: - return a + (b - a) * t - - result = [] - for channel in range(4): - c00 = pixels[y0][x0][channel] - c10 = pixels[y0][x1][channel] - c01 = pixels[y1][x0][channel] - c11 = pixels[y1][x1][channel] - top = lerp(c00, c10, dx) - bottom = lerp(c01, c11, dx) - result.append(int(round(lerp(top, bottom, dy)))) - return tuple(result) - - -def resize_image(pixels: list[list[tuple[int, int, int, int]]], target: int) -> list[list[tuple[int, int, int, int]]]: - src_height = len(pixels) - src_width = len(pixels[0]) - scale = min(target / src_width, target / src_height) - dest_width = max(1, int(round(src_width * scale))) - dest_height = max(1, int(round(src_height * scale))) - offset_x = (target - dest_width) // 2 - offset_y = (target - dest_height) // 2 - - background = (0, 0, 0, 0) - canvas = [[background for _ in range(target)] for _ in range(target)] - - for dy in range(dest_height): - src_y = (dy + 0.5) / scale - 0.5 - for dx in range(dest_width): - src_x = (dx + 0.5) / scale - 0.5 - canvas[offset_y + dy][offset_x + dx] = bilinear_sample(pixels, src_x, src_y) - return canvas - - -def build_ico(output: Path, png_paths: list[Path]) -> None: - entries = [] - offset = 6 + 16 * len(png_paths) - for path in png_paths: - data = path.read_bytes() - width, height, _ = read_png(path) - entries.append( - { - "width": width if width < 256 else 0, - "height": height if height < 256 else 0, - "size": len(data), - "offset": offset, - "payload": data, - } - ) - offset += len(data) - - header = struct.pack(" None: - width, height, pixels = read_png(BASE_IMAGE) - if width != height: - raise ValueError("Base icon must be square") - - generated: list[Path] = [] - for size in TARGET_SIZES: - resized = resize_image(pixels, size) - out_path = ICON_DIR / f"icon-{size}.png" - write_png(out_path, size, size, resized) - generated.append(out_path) - print(f"Generated {out_path} ({size}x{size})") - - largest = max(generated, key=lambda p: int(p.stem.split("-")[-1])) - (ICON_DIR / "icon.png").write_bytes(largest.read_bytes()) - - ico_sources = sorted( - [p for p in generated if int(p.stem.split("-")[-1]) <= 256], - key=lambda p: int(p.stem.split("-")[-1]), - ) - build_ico(ICON_DIR / "icon.ico", ico_sources) - print("icon.ico rebuilt.") - - -if __name__ == "__main__": - main() - diff --git a/apps/desktop/scripts/png_to_bmp.py b/apps/desktop/scripts/png_to_bmp.py deleted file mode 100644 index cc5c11e..0000000 --- a/apps/desktop/scripts/png_to_bmp.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python3 -""" -Utility script to convert a PNG file (non-interlaced, 8-bit RGBA/RGB) -into a 24-bit BMP with optional letterboxing resize. - -The script is intentionally lightweight and relies only on Python's -standard library so it can run in constrained build environments. -""" - -from __future__ import annotations - -import argparse -import struct -import sys -import zlib -from pathlib import Path - - -PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" - - -def parse_png(path: Path): - data = path.read_bytes() - if not data.startswith(PNG_SIGNATURE): - raise ValueError("Input is not a PNG file") - - idx = len(PNG_SIGNATURE) - width = height = bit_depth = color_type = None - compressed = bytearray() - interlaced = False - - while idx < len(data): - if idx + 8 > len(data): - raise ValueError("Corrupted PNG (unexpected EOF)") - length = struct.unpack(">I", data[idx : idx + 4])[0] - idx += 4 - chunk_type = data[idx : idx + 4] - idx += 4 - chunk_data = data[idx : idx + length] - idx += length - crc = data[idx : idx + 4] # noqa: F841 - crc skipped (validated by reader) - idx += 4 - - if chunk_type == b"IHDR": - width, height, bit_depth, color_type, compression, filter_method, interlace = struct.unpack( - ">IIBBBBB", chunk_data - ) - if compression != 0 or filter_method != 0: - raise ValueError("Unsupported PNG compression/filter method") - interlaced = interlace != 0 - elif chunk_type == b"IDAT": - compressed.extend(chunk_data) - elif chunk_type == b"IEND": - break - - if interlaced: - raise ValueError("Interlaced PNGs are not supported by this script") - if bit_depth != 8: - raise ValueError(f"Unsupported bit depth: {bit_depth}") - if color_type not in (2, 6): - raise ValueError(f"Unsupported color type: {color_type}") - - raw = zlib.decompress(bytes(compressed)) - bytes_per_pixel = 3 if color_type == 2 else 4 - stride = width * bytes_per_pixel - expected = (stride + 1) * height - if len(raw) != expected: - raise ValueError("Corrupted PNG data") - - # Apply PNG scanline filters - image = bytearray(width * height * 4) # Force RGBA output - prev_row = [0] * (stride) - - def paeth(a, b, c): - p = a + b - c - pa = abs(p - a) - pb = abs(p - b) - pc = abs(p - c) - if pa <= pb and pa <= pc: - return a - if pb <= pc: - return b - return c - - out_idx = 0 - for y in range(height): - offset = y * (stride + 1) - filter_type = raw[offset] - row = bytearray(raw[offset + 1 : offset + 1 + stride]) - if filter_type == 1: # Sub - for i in range(stride): - left = row[i - bytes_per_pixel] if i >= bytes_per_pixel else 0 - row[i] = (row[i] + left) & 0xFF - elif filter_type == 2: # Up - for i in range(stride): - row[i] = (row[i] + prev_row[i]) & 0xFF - elif filter_type == 3: # Average - for i in range(stride): - left = row[i - bytes_per_pixel] if i >= bytes_per_pixel else 0 - up = prev_row[i] - row[i] = (row[i] + ((left + up) >> 1)) & 0xFF - elif filter_type == 4: # Paeth - for i in range(stride): - left = row[i - bytes_per_pixel] if i >= bytes_per_pixel else 0 - up = prev_row[i] - up_left = prev_row[i - bytes_per_pixel] if i >= bytes_per_pixel else 0 - row[i] = (row[i] + paeth(left, up, up_left)) & 0xFF - elif filter_type != 0: - raise ValueError(f"Unsupported PNG filter type: {filter_type}") - - # Convert to RGBA - for x in range(width): - if color_type == 2: - r, g, b = row[x * 3 : x * 3 + 3] - a = 255 - else: - r, g, b, a = row[x * 4 : x * 4 + 4] - image[out_idx : out_idx + 4] = bytes((r, g, b, a)) - out_idx += 4 - - prev_row = list(row) - - return width, height, image - - -def resize_with_letterbox(image, width, height, target_w, target_h, background, scale_factor=1.0): - if width == target_w and height == target_h and abs(scale_factor - 1.0) < 1e-6: - return image, width, height - - bg_r, bg_g, bg_b = background - base_scale = min(target_w / width, target_h / height) - base_scale *= scale_factor - base_scale = max(base_scale, 1 / max(width, height)) # avoid zero / collapse - scaled_w = max(1, int(round(width * base_scale))) - scaled_h = max(1, int(round(height * base_scale))) - - output = bytearray(target_w * target_h * 4) - # Fill background - for i in range(0, len(output), 4): - output[i : i + 4] = bytes((bg_r, bg_g, bg_b, 255)) - - offset_x = (target_w - scaled_w) // 2 - offset_y = (target_h - scaled_h) // 2 - - for y in range(scaled_h): - src_y = min(height - 1, int(round(y / base_scale))) - for x in range(scaled_w): - src_x = min(width - 1, int(round(x / base_scale))) - src_idx = (src_y * width + src_x) * 4 - dst_idx = ((y + offset_y) * target_w + (x + offset_x)) * 4 - output[dst_idx : dst_idx + 4] = image[src_idx : src_idx + 4] - - return output, target_w, target_h - - -def blend_to_rgb(image): - rgb = bytearray(len(image) // 4 * 3) - for i in range(0, len(image), 4): - r, g, b, a = image[i : i + 4] - if a == 255: - rgb[(i // 4) * 3 : (i // 4) * 3 + 3] = bytes((b, g, r)) # BMP stores BGR - else: - alpha = a / 255.0 - bg = (255, 255, 255) - rr = int(round(r * alpha + bg[0] * (1 - alpha))) - gg = int(round(g * alpha + bg[1] * (1 - alpha))) - bb = int(round(b * alpha + bg[2] * (1 - alpha))) - rgb[(i // 4) * 3 : (i // 4) * 3 + 3] = bytes((bb, gg, rr)) - return rgb - - -def write_bmp(path: Path, width: int, height: int, rgb: bytearray): - row_stride = (width * 3 + 3) & ~3 # align to 4 bytes - padding = row_stride - width * 3 - pixel_data = bytearray() - - for y in range(height - 1, -1, -1): - start = y * width * 3 - end = start + width * 3 - pixel_data.extend(rgb[start:end]) - if padding: - pixel_data.extend(b"\0" * padding) - - file_size = 14 + 40 + len(pixel_data) - header = struct.pack("<2sIHHI", b"BM", file_size, 0, 0, 14 + 40) - dib_header = struct.pack( - " tuple[int, int]: - if not data.startswith(PNG_SIGNATURE): - raise ValueError("All inputs must be PNG files.") - width, height = struct.unpack(">II", data[16:24]) - return width, height - - -def build_icon(png_paths: list[Path], output: Path) -> None: - png_data = [p.read_bytes() for p in png_paths] - entries = [] - offset = 6 + 16 * len(png_data) # icon header + entries - - for data in png_data: - width, height = read_png_dimensions(data) - entry = { - "width": width if width < 256 else 0, - "height": height if height < 256 else 0, - "colors": 0, - "reserved": 0, - "planes": 1, - "bit_count": 32, - "size": len(data), - "offset": offset, - "data": data, - } - entries.append(entry) - offset += entry["size"] - - header = struct.pack(" None: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("output", type=Path) - parser.add_argument("inputs", nargs="+", type=Path) - args = parser.parse_args() - - if not args.inputs: - raise SystemExit("Provide at least one PNG input.") - - build_icon(args.inputs, args.output) - - -if __name__ == "__main__": - main() diff --git a/apps/desktop/scripts/tauri-with-stub.mjs b/apps/desktop/scripts/tauri-with-stub.mjs deleted file mode 100644 index add717a..0000000 --- a/apps/desktop/scripts/tauri-with-stub.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import { spawn } from "node:child_process" -import { fileURLToPath } from "node:url" -import { dirname, resolve } from "node:path" -import { existsSync } from "node:fs" - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const appRoot = resolve(__dirname, "..") - -const pathKey = process.platform === "win32" ? "Path" : "PATH" -const currentPath = process.env[pathKey] ?? process.env[pathKey.toUpperCase()] ?? "" -const separator = process.platform === "win32" ? ";" : ":" -const stubDir = resolve(__dirname) - -process.env[pathKey] = [stubDir, currentPath].filter(Boolean).join(separator) -if (pathKey !== "PATH") { - process.env.PATH = process.env[pathKey] -} - -if (!process.env.TAURI_BUNDLE_TARGETS) { - if (process.platform === "linux") { - process.env.TAURI_BUNDLE_TARGETS = "deb rpm" - } else if (process.platform === "win32") { - process.env.TAURI_BUNDLE_TARGETS = "nsis" - } -} - -// Assinatura: fallback seguro para builds locais/CI. Em prod, pode sobrescrever por env. -if (!process.env.TAURI_SIGNING_PRIVATE_KEY) { - process.env.TAURI_SIGNING_PRIVATE_KEY = - "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5WkhWOUtzd1BvV0ZlSjEvNzYwaHYxdEloNnV4cmZlNGhha1BNbmNtZEkrZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQS9JbCtsd3VFbHN4empFRUNiU0dva1hKK3ZYUzE2S1V6Q1FhYkRUWGtGMTBkUmJodi9PaXVub3hEMisyTXJoYU5UeEdwZU9aMklacG9ualNWR1NaTm1PMVBpVXYrNTltZU1YOFdwYzdkOHd2STFTc0x4ZktpNXFENnFTdW0xNzY3WC9EcGlIRGFmK2c9Cg==" -} -if (!process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD) { - process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "revertech" -} - -const winTauriPath = resolve(appRoot, "node_modules", ".bin", "tauri.cmd") -const usingWinTauri = process.platform === "win32" && existsSync(winTauriPath) -const executable = process.platform === "win32" && usingWinTauri ? "cmd.exe" : "tauri" -const args = - process.platform === "win32" && usingWinTauri - ? ["/C", winTauriPath, ...process.argv.slice(2)] - : process.argv.slice(2) -const child = spawn(executable, args, { - stdio: "inherit", - shell: false, - cwd: appRoot, -}) - -child.on("exit", (code, signal) => { - if (signal) { - process.kill(process.pid, signal) - } else { - process.exit(code ?? 0) - } -}) diff --git a/apps/desktop/scripts/xdg-open b/apps/desktop/scripts/xdg-open deleted file mode 100644 index b081d99..0000000 --- a/apps/desktop/scripts/xdg-open +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# Minimal stub to satisfy tools that expect xdg-open during bundling. -# Fails silently when the real binary is unavailable. -if command -v xdg-open >/dev/null 2>&1; then - exec xdg-open "$@" -else - exit 0 -fi diff --git a/apps/desktop/service/Cargo.lock b/apps/desktop/service/Cargo.lock deleted file mode 100644 index da860fc..0000000 --- a/apps/desktop/service/Cargo.lock +++ /dev/null @@ -1,1931 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - -[[package]] -name = "cc" -version = "1.2.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -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.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "doctest-file" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "interprocess" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" -dependencies = [ - "doctest-file", - "futures-core", - "libc", - "recvmsg", - "tokio", - "widestring", - "windows-sys 0.52.0", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -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 = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[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.17", - "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", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.17", - "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.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[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", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "raven-service" -version = "0.1.0" -dependencies = [ - "chrono", - "interprocess", - "once_cell", - "parking_lot", - "reqwest", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "tokio", - "tracing", - "tracing-subscriber", - "uuid", - "windows", - "windows-service", - "winreg", -] - -[[package]] -name = "recvmsg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "reqwest" -version = "0.12.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "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", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "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.16", - "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 = "rustls" -version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "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.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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 = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[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.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" -dependencies = [ - "js-sys", - "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 = "webpki-roots" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-service" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a" -dependencies = [ - "bitflags", - "widestring", - "windows-sys 0.52.0", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/apps/desktop/service/Cargo.toml b/apps/desktop/service/Cargo.toml deleted file mode 100644 index a1334d5..0000000 --- a/apps/desktop/service/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "raven-service" -version = "0.1.0" -description = "Raven Windows Service - Executa operacoes privilegiadas para o Raven Desktop" -authors = ["Esdras Renan"] -edition = "2021" - -[[bin]] -name = "raven-service" -path = "src/main.rs" - -[dependencies] -# Windows Service -windows-service = "0.7" - -# Async runtime -tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "io-util", "net", "signal"] } - -# IPC via Named Pipes -interprocess = { version = "2", features = ["tokio"] } - -# Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Windows Registry -winreg = "0.55" - -# Error handling -thiserror = "1.0" - -# HTTP client (para RustDesk) -reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"], default-features = false } - -# Date/time -chrono = { version = "0.4", features = ["serde"] } - -# Crypto (para RustDesk ID) -sha2 = "0.10" - -# UUID para request IDs -uuid = { version = "1", features = ["v4"] } - -# Parking lot para locks -parking_lot = "0.12" - -# Once cell para singletons -once_cell = "1.19" - -[target.'cfg(windows)'.dependencies] -windows = { version = "0.58", features = [ - "Win32_Foundation", - "Win32_Security", - "Win32_System_Services", - "Win32_System_Threading", - "Win32_System_Pipes", - "Win32_System_IO", - "Win32_System_SystemServices", - "Win32_Storage_FileSystem", -] } - -[profile.release] -opt-level = "z" -lto = true -codegen-units = 1 -strip = true diff --git a/apps/desktop/service/src/ipc.rs b/apps/desktop/service/src/ipc.rs deleted file mode 100644 index 26091b6..0000000 --- a/apps/desktop/service/src/ipc.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! Modulo IPC - Servidor de Named Pipes -//! -//! Implementa comunicacao entre o Raven UI e o Raven Service -//! usando Named Pipes do Windows com protocolo JSON-RPC simplificado. - -use crate::{rustdesk, usb_policy}; -use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader, Write}; -use thiserror::Error; -use tracing::{debug, info, warn}; - -#[derive(Debug, Error)] -pub enum IpcError { - #[error("Erro de IO: {0}")] - Io(#[from] std::io::Error), - - #[error("Erro de serializacao: {0}")] - Json(#[from] serde_json::Error), -} - -/// Requisicao JSON-RPC simplificada -#[derive(Debug, Deserialize)] -pub struct Request { - pub id: String, - pub method: String, - #[serde(default)] - pub params: serde_json::Value, -} - -/// Resposta JSON-RPC simplificada -#[derive(Debug, Serialize)] -pub struct Response { - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, -} - -#[derive(Debug, Serialize)] -pub struct ErrorResponse { - pub code: i32, - pub message: String, -} - -impl Response { - pub fn success(id: String, result: serde_json::Value) -> Self { - Self { - id, - result: Some(result), - error: None, - } - } - - pub fn error(id: String, code: i32, message: String) -> Self { - Self { - id, - result: None, - error: Some(ErrorResponse { code, message }), - } - } -} - -/// Inicia o servidor de Named Pipes -pub async fn run_server(pipe_name: &str) -> Result<(), IpcError> { - info!("Iniciando servidor IPC em: {}", pipe_name); - - loop { - match accept_connection(pipe_name).await { - Ok(()) => { - debug!("Conexao processada com sucesso"); - } - Err(e) => { - warn!("Erro ao processar conexao: {}", e); - } - } - } -} - -/// Aceita uma conexao e processa requisicoes -async fn accept_connection(pipe_name: &str) -> Result<(), IpcError> { - use windows::Win32::Foundation::INVALID_HANDLE_VALUE; - use windows::Win32::Security::{ - InitializeSecurityDescriptor, SetSecurityDescriptorDacl, - PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR, - }; - use windows::Win32::Storage::FileSystem::PIPE_ACCESS_DUPLEX; - use windows::Win32::System::Pipes::{ - ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe, - PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, - }; - use windows::Win32::System::SystemServices::SECURITY_DESCRIPTOR_REVISION; - use windows::core::PCWSTR; - - // Cria o named pipe com seguranca que permite acesso a todos os usuarios - let pipe_name_wide: Vec = pipe_name.encode_utf16().chain(std::iter::once(0)).collect(); - - // Cria security descriptor com DACL nulo (permite acesso a todos) - let mut sd = SECURITY_DESCRIPTOR::default(); - unsafe { - let sd_ptr = PSECURITY_DESCRIPTOR(&mut sd as *mut _ as *mut _); - let _ = InitializeSecurityDescriptor(sd_ptr, SECURITY_DESCRIPTOR_REVISION); - // DACL nulo = acesso irrestrito - let _ = SetSecurityDescriptorDacl(sd_ptr, true, None, false); - } - - let sa = SECURITY_ATTRIBUTES { - nLength: std::mem::size_of::() as u32, - lpSecurityDescriptor: &mut sd as *mut _ as *mut _, - bInheritHandle: false.into(), - }; - - let pipe_handle = unsafe { - CreateNamedPipeW( - PCWSTR::from_raw(pipe_name_wide.as_ptr()), - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, - 4096, // out buffer - 4096, // in buffer - 0, // default timeout - Some(&sa), // seguranca permissiva - ) - }; - - // Verifica se o handle e valido - if pipe_handle == INVALID_HANDLE_VALUE { - return Err(IpcError::Io(std::io::Error::last_os_error())); - } - - // Aguarda conexao de um cliente - info!("Aguardando conexao de cliente..."); - let connect_result = unsafe { - ConnectNamedPipe(pipe_handle, None) - }; - - if let Err(e) = connect_result { - // ERROR_PIPE_CONNECTED (535) significa que o cliente ja estava conectado - // o que e aceitavel - let error_code = e.code().0 as u32; - if error_code != 535 { - warn!("Erro ao aguardar conexao: {:?}", e); - } - } - - info!("Cliente conectado"); - - // Processa requisicoes do cliente - let result = process_client(pipe_handle); - - // Desconecta o cliente - unsafe { - let _ = DisconnectNamedPipe(pipe_handle); - } - - result -} - -/// Processa requisicoes de um cliente conectado -fn process_client(pipe_handle: windows::Win32::Foundation::HANDLE) -> Result<(), IpcError> { - use std::os::windows::io::{FromRawHandle, RawHandle}; - use std::fs::File; - - // Cria File handle a partir do pipe - let raw_handle = pipe_handle.0 as RawHandle; - let file = unsafe { File::from_raw_handle(raw_handle) }; - - let reader = BufReader::new(file.try_clone()?); - let mut writer = file; - - // Le linhas (cada linha e uma requisicao JSON) - for line in reader.lines() { - let line = match line { - Ok(l) => l, - Err(e) => { - if e.kind() == std::io::ErrorKind::BrokenPipe { - info!("Cliente desconectou"); - break; - } - return Err(e.into()); - } - }; - - if line.is_empty() { - continue; - } - - debug!("Requisicao recebida: {}", line); - - // Parse da requisicao - let response = match serde_json::from_str::(&line) { - Ok(request) => handle_request(request), - Err(e) => Response::error( - "unknown".to_string(), - -32700, - format!("Parse error: {}", e), - ), - }; - - // Serializa e envia resposta - let response_json = serde_json::to_string(&response)?; - debug!("Resposta: {}", response_json); - - writeln!(writer, "{}", response_json)?; - writer.flush()?; - } - - // IMPORTANTE: Nao fechar o handle aqui, pois DisconnectNamedPipe precisa dele - std::mem::forget(writer); - - Ok(()) -} - -/// Processa uma requisicao e retorna a resposta -fn handle_request(request: Request) -> Response { - info!("Processando metodo: {}", request.method); - - match request.method.as_str() { - "health_check" => handle_health_check(request.id), - "apply_usb_policy" => handle_apply_usb_policy(request.id, request.params), - "get_usb_policy" => handle_get_usb_policy(request.id), - "provision_rustdesk" => handle_provision_rustdesk(request.id, request.params), - "get_rustdesk_status" => handle_get_rustdesk_status(request.id), - _ => Response::error( - request.id, - -32601, - format!("Metodo nao encontrado: {}", request.method), - ), - } -} - -// ============================================================================= -// Handlers de Requisicoes -// ============================================================================= - -fn handle_health_check(id: String) -> Response { - Response::success( - id, - serde_json::json!({ - "status": "ok", - "service": "RavenService", - "version": env!("CARGO_PKG_VERSION"), - "timestamp": chrono::Utc::now().timestamp_millis() - }), - ) -} - -fn handle_apply_usb_policy(id: String, params: serde_json::Value) -> Response { - let policy = match params.get("policy").and_then(|p| p.as_str()) { - Some(p) => p, - None => { - return Response::error(id, -32602, "Parametro 'policy' e obrigatorio".to_string()) - } - }; - - match usb_policy::apply_policy(policy) { - Ok(result) => Response::success(id, serde_json::to_value(result).unwrap()), - Err(e) => Response::error(id, -32000, format!("Erro ao aplicar politica: {}", e)), - } -} - -fn handle_get_usb_policy(id: String) -> Response { - match usb_policy::get_current_policy() { - Ok(policy) => Response::success( - id, - serde_json::json!({ - "policy": policy - }), - ), - Err(e) => Response::error(id, -32000, format!("Erro ao obter politica: {}", e)), - } -} - -fn handle_provision_rustdesk(id: String, params: serde_json::Value) -> Response { - let config_string = params.get("config").and_then(|c| c.as_str()).map(String::from); - let password = params.get("password").and_then(|p| p.as_str()).map(String::from); - let machine_id = params.get("machineId").and_then(|m| m.as_str()).map(String::from); - - match rustdesk::ensure_rustdesk(config_string.as_deref(), password.as_deref(), machine_id.as_deref()) { - Ok(result) => Response::success(id, serde_json::to_value(result).unwrap()), - Err(e) => Response::error(id, -32000, format!("Erro ao provisionar RustDesk: {}", e)), - } -} - -fn handle_get_rustdesk_status(id: String) -> Response { - match rustdesk::get_status() { - Ok(status) => Response::success(id, serde_json::to_value(status).unwrap()), - Err(e) => Response::error(id, -32000, format!("Erro ao obter status: {}", e)), - } -} diff --git a/apps/desktop/service/src/main.rs b/apps/desktop/service/src/main.rs deleted file mode 100644 index 208e22c..0000000 --- a/apps/desktop/service/src/main.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! Raven Service - Servico Windows para operacoes privilegiadas -//! -//! Este servico roda como LocalSystem e executa operacoes que requerem -//! privilegios de administrador, como: -//! - Aplicar politicas de USB -//! - Provisionar e configurar RustDesk -//! - Modificar chaves de registro em HKEY_LOCAL_MACHINE -//! -//! O app Raven UI comunica com este servico via Named Pipes. - -mod ipc; -mod rustdesk; -mod usb_policy; - -use std::ffi::OsString; -use std::time::Duration; -use tracing::{error, info}; -use windows_service::{ - define_windows_service, - service::{ - ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, - ServiceType, - }, - service_control_handler::{self, ServiceControlHandlerResult}, - service_dispatcher, -}; - -const SERVICE_NAME: &str = "RavenService"; -const SERVICE_DISPLAY_NAME: &str = "Raven Desktop Service"; -const SERVICE_DESCRIPTION: &str = "Servico do Raven Desktop para operacoes privilegiadas (USB, RustDesk)"; -const PIPE_NAME: &str = r"\\.\pipe\RavenService"; - -define_windows_service!(ffi_service_main, service_main); - -fn main() -> Result<(), Box> { - // Configura logging - init_logging(); - - // Verifica argumentos de linha de comando - let args: Vec = std::env::args().collect(); - - if args.len() > 1 { - match args[1].as_str() { - "install" => { - install_service()?; - return Ok(()); - } - "uninstall" => { - uninstall_service()?; - return Ok(()); - } - "run" => { - // Modo de teste: roda sem registrar como servico - info!("Executando em modo de teste (nao como servico)"); - run_standalone()?; - return Ok(()); - } - _ => {} - } - } - - // Inicia como servico Windows - info!("Iniciando Raven Service..."); - service_dispatcher::start(SERVICE_NAME, ffi_service_main)?; - Ok(()) -} - -fn init_logging() { - use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - - // Tenta criar diretorio de logs - let log_dir = std::env::var("PROGRAMDATA") - .map(|p| std::path::PathBuf::from(p).join("RavenService").join("logs")) - .unwrap_or_else(|_| std::path::PathBuf::from("C:\\ProgramData\\RavenService\\logs")); - - let _ = std::fs::create_dir_all(&log_dir); - - // Arquivo de log - let log_file = log_dir.join("service.log"); - let file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_file) - .ok(); - - let filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new("info")); - - if let Some(file) = file { - tracing_subscriber::registry() - .with(filter) - .with(fmt::layer().with_writer(file).with_ansi(false)) - .init(); - } else { - tracing_subscriber::registry() - .with(filter) - .with(fmt::layer()) - .init(); - } -} - -fn service_main(arguments: Vec) { - if let Err(e) = run_service(arguments) { - error!("Erro ao executar servico: {}", e); - } -} - -fn run_service(_arguments: Vec) -> Result<(), Box> { - info!("Servico iniciando..."); - - // Canal para shutdown - let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); - let shutdown_tx = std::sync::Arc::new(std::sync::Mutex::new(Some(shutdown_tx))); - - // Registra handler de controle do servico - let shutdown_tx_clone = shutdown_tx.clone(); - let status_handle = service_control_handler::register(SERVICE_NAME, move |control| { - match control { - ServiceControl::Stop | ServiceControl::Shutdown => { - info!("Recebido comando de parada"); - if let Ok(mut guard) = shutdown_tx_clone.lock() { - if let Some(tx) = guard.take() { - let _ = tx.send(()); - } - } - ServiceControlHandlerResult::NoError - } - ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, - _ => ServiceControlHandlerResult::NotImplemented, - } - })?; - - // Atualiza status para Running - status_handle.set_service_status(ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Running, - controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN, - exit_code: ServiceExitCode::Win32(0), - checkpoint: 0, - wait_hint: Duration::default(), - process_id: None, - })?; - - info!("Servico em execucao, aguardando conexoes..."); - - // Cria runtime Tokio - let runtime = tokio::runtime::Runtime::new()?; - - // Executa servidor IPC - runtime.block_on(async { - tokio::select! { - result = ipc::run_server(PIPE_NAME) => { - if let Err(e) = result { - error!("Erro no servidor IPC: {}", e); - } - } - _ = async { - let _ = shutdown_rx.await; - } => { - info!("Shutdown solicitado"); - } - } - }); - - // Atualiza status para Stopped - status_handle.set_service_status(ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Stopped, - controls_accepted: ServiceControlAccept::empty(), - exit_code: ServiceExitCode::Win32(0), - checkpoint: 0, - wait_hint: Duration::default(), - process_id: None, - })?; - - info!("Servico parado"); - Ok(()) -} - -fn run_standalone() -> Result<(), Box> { - let runtime = tokio::runtime::Runtime::new()?; - - runtime.block_on(async { - info!("Servidor IPC iniciando em modo standalone..."); - - tokio::select! { - result = ipc::run_server(PIPE_NAME) => { - if let Err(e) = result { - error!("Erro no servidor IPC: {}", e); - } - } - _ = tokio::signal::ctrl_c() => { - info!("Ctrl+C recebido, encerrando..."); - } - } - }); - - Ok(()) -} - -fn install_service() -> Result<(), Box> { - use windows_service::{ - service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType}, - service_manager::{ServiceManager, ServiceManagerAccess}, - }; - - info!("Instalando servico..."); - - let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CREATE_SERVICE)?; - - let exe_path = std::env::current_exe()?; - - let service_info = ServiceInfo { - name: OsString::from(SERVICE_NAME), - display_name: OsString::from(SERVICE_DISPLAY_NAME), - service_type: ServiceType::OWN_PROCESS, - start_type: ServiceStartType::AutoStart, - error_control: ServiceErrorControl::Normal, - executable_path: exe_path, - launch_arguments: vec![], - dependencies: vec![], - account_name: None, // LocalSystem - account_password: None, - }; - - let service = manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?; - - // Define descricao - service.set_description(SERVICE_DESCRIPTION)?; - - info!("Servico instalado com sucesso: {}", SERVICE_NAME); - println!("Servico '{}' instalado com sucesso!", SERVICE_DISPLAY_NAME); - println!("Para iniciar: sc start {}", SERVICE_NAME); - - Ok(()) -} - -fn uninstall_service() -> Result<(), Box> { - use windows_service::{ - service::ServiceAccess, - service_manager::{ServiceManager, ServiceManagerAccess}, - }; - - info!("Desinstalando servico..."); - - let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?; - - let service = manager.open_service( - SERVICE_NAME, - ServiceAccess::STOP | ServiceAccess::DELETE | ServiceAccess::QUERY_STATUS, - )?; - - // Tenta parar o servico primeiro - let status = service.query_status()?; - if status.current_state != ServiceState::Stopped { - info!("Parando servico..."); - let _ = service.stop(); - std::thread::sleep(Duration::from_secs(2)); - } - - // Remove o servico - service.delete()?; - - info!("Servico desinstalado com sucesso"); - println!("Servico '{}' removido com sucesso!", SERVICE_DISPLAY_NAME); - - Ok(()) -} diff --git a/apps/desktop/service/src/rustdesk.rs b/apps/desktop/service/src/rustdesk.rs deleted file mode 100644 index 0df60aa..0000000 --- a/apps/desktop/service/src/rustdesk.rs +++ /dev/null @@ -1,846 +0,0 @@ -//! Modulo RustDesk - Provisionamento e gerenciamento do RustDesk -//! -//! Gerencia a instalacao, configuracao e provisionamento do RustDesk. -//! Como o servico roda como LocalSystem, nao precisa de elevacao. - -use chrono::Utc; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use reqwest::blocking::Client; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::env; -use std::ffi::OsStr; -use std::fs::{self, File, OpenOptions}; -use std::io::{self, Write}; -use std::os::windows::process::CommandExt; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use std::thread; -use std::time::Duration; -use thiserror::Error; -use tracing::{error, info, warn}; - -const RELEASES_API: &str = "https://api.github.com/repos/rustdesk/rustdesk/releases/latest"; -const USER_AGENT: &str = "RavenService/1.0"; -const SERVER_HOST: &str = "rust.rever.com.br"; -const SERVER_KEY: &str = "0mxocQKmK6GvTZQYKgjrG9tlNkKOqf81gKgqwAmnZuI="; -const DEFAULT_PASSWORD: &str = "FMQ9MA>e73r.FI> = Lazy::new(|| Mutex::new(())); - -#[derive(Debug, Error)] -pub enum RustdeskError { - #[error("HTTP error: {0}")] - Http(#[from] reqwest::Error), - - #[error("I/O error: {0}")] - Io(#[from] io::Error), - - #[error("Release asset nao encontrado para Windows x86_64")] - AssetMissing, - - #[error("Falha ao executar comando {command}: status {status:?}")] - CommandFailed { command: String, status: Option }, - - #[error("Falha ao detectar ID do RustDesk")] - MissingId, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RustdeskResult { - pub id: String, - pub password: String, - pub installed_version: Option, - pub updated: bool, - pub last_provisioned_at: i64, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RustdeskStatus { - pub installed: bool, - pub running: bool, - pub id: Option, - pub version: Option, -} - -#[derive(Debug, Deserialize)] -struct ReleaseAsset { - name: String, - browser_download_url: String, -} - -#[derive(Debug, Deserialize)] -struct ReleaseResponse { - tag_name: String, - assets: Vec, -} - -/// Provisiona o RustDesk -pub fn ensure_rustdesk( - config_string: Option<&str>, - password_override: Option<&str>, - machine_id: Option<&str>, -) -> Result { - let _guard = PROVISION_MUTEX.lock(); - info!("Iniciando provisionamento do RustDesk"); - - // Prepara ACLs dos diretorios de servico - if let Err(e) = ensure_service_profiles_writable() { - warn!("Aviso ao preparar ACL: {}", e); - } - - // Le ID existente antes de qualquer limpeza - let preserved_remote_id = read_remote_id_from_profiles(); - if let Some(ref id) = preserved_remote_id { - info!("ID existente preservado: {}", id); - } - - let exe_path = detect_executable_path(); - let (installed_version, freshly_installed) = ensure_installed(&exe_path)?; - - info!( - "RustDesk {}: {}", - if freshly_installed { "instalado" } else { "ja presente" }, - exe_path.display() - ); - - // Para processos existentes - let _ = stop_rustdesk_processes(); - - // Limpa perfis apenas se instalacao fresca - if freshly_installed { - let _ = purge_existing_rustdesk_profiles(); - } - - // Aplica configuracao - if let Some(config) = config_string.filter(|c| !c.trim().is_empty()) { - if let Err(e) = run_with_args(&exe_path, &["--config", config]) { - warn!("Falha ao aplicar config inline: {}", e); - } - } else { - let config_path = write_config_files()?; - if let Err(e) = apply_config(&exe_path, &config_path) { - warn!("Falha ao aplicar config via CLI: {}", e); - } - } - - // Define senha - let password = password_override - .map(|v| v.trim().to_string()) - .filter(|v| !v.is_empty()) - .unwrap_or_else(|| DEFAULT_PASSWORD.to_string()); - - if let Err(e) = set_password(&exe_path, &password) { - warn!("Falha ao definir senha: {}", e); - } else { - let _ = ensure_password_files(&password); - let _ = propagate_password_profile(); - } - - // Define ID customizado - let custom_id = if let Some(ref existing_id) = preserved_remote_id { - if !freshly_installed { - Some(existing_id.clone()) - } else { - define_custom_id(&exe_path, machine_id) - } - } else { - define_custom_id(&exe_path, machine_id) - }; - - // Inicia servico - if let Err(e) = ensure_service_running(&exe_path) { - warn!("Falha ao iniciar servico: {}", e); - } - - // Obtem ID final - let final_id = match query_id_with_retries(&exe_path, 5) { - Ok(id) => id, - Err(_) => { - read_remote_id_from_profiles() - .or_else(|| custom_id.clone()) - .ok_or(RustdeskError::MissingId)? - } - }; - - // Garante ID em todos os arquivos - ensure_remote_id_files(&final_id); - - let version = query_version(&exe_path).ok().or(installed_version); - let last_provisioned_at = Utc::now().timestamp_millis(); - - info!("Provisionamento concluido. ID: {}, Versao: {:?}", final_id, version); - - Ok(RustdeskResult { - id: final_id, - password, - installed_version: version, - updated: freshly_installed, - last_provisioned_at, - }) -} - -/// Retorna status do RustDesk -pub fn get_status() -> Result { - let exe_path = detect_executable_path(); - let installed = exe_path.exists(); - - let running = if installed { - query_service_state().map(|s| s == "running").unwrap_or(false) - } else { - false - }; - - let id = if installed { - query_id(&exe_path).ok().or_else(read_remote_id_from_profiles) - } else { - None - }; - - let version = if installed { - query_version(&exe_path).ok() - } else { - None - }; - - Ok(RustdeskStatus { - installed, - running, - id, - version, - }) -} - -// ============================================================================= -// Funcoes Auxiliares -// ============================================================================= - -fn detect_executable_path() -> PathBuf { - let program_files = env::var("PROGRAMFILES").unwrap_or_else(|_| "C:/Program Files".to_string()); - Path::new(&program_files).join("RustDesk").join("rustdesk.exe") -} - -fn ensure_installed(exe_path: &Path) -> Result<(Option, bool), RustdeskError> { - if exe_path.exists() { - return Ok((None, false)); - } - - let cache_root = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())) - .join(CACHE_DIR_NAME); - fs::create_dir_all(&cache_root)?; - - let (installer_path, version_tag) = download_latest_installer(&cache_root)?; - run_installer(&installer_path)?; - thread::sleep(Duration::from_secs(20)); - - Ok((Some(version_tag), true)) -} - -fn download_latest_installer(cache_root: &Path) -> Result<(PathBuf, String), RustdeskError> { - let client = Client::builder() - .user_agent(USER_AGENT) - .timeout(Duration::from_secs(60)) - .build()?; - - let release: ReleaseResponse = client.get(RELEASES_API).send()?.error_for_status()?.json()?; - - let asset = release - .assets - .iter() - .find(|a| a.name.ends_with("x86_64.exe")) - .ok_or(RustdeskError::AssetMissing)?; - - let target_path = cache_root.join(&asset.name); - if target_path.exists() { - return Ok((target_path, release.tag_name)); - } - - info!("Baixando RustDesk: {}", asset.name); - let mut response = client.get(&asset.browser_download_url).send()?.error_for_status()?; - let mut output = File::create(&target_path)?; - response.copy_to(&mut output)?; - - Ok((target_path, release.tag_name)) -} - -fn run_installer(installer_path: &Path) -> Result<(), RustdeskError> { - let status = hidden_command(installer_path) - .arg("--silent-install") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --silent-install", installer_path.display()), - status: status.code(), - }); - } - Ok(()) -} - -fn program_data_config_dir() -> PathBuf { - PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())) - .join("RustDesk") - .join("config") -} - -/// Retorna todos os diretorios AppData\Roaming\RustDesk\config de usuarios do sistema -/// Como o servico roda como LocalSystem, precisamos enumerar os profiles de usuarios -fn all_user_appdata_config_dirs() -> Vec { - let mut dirs = Vec::new(); - - // Enumera C:\Users\*\AppData\Roaming\RustDesk\config - let users_dir = Path::new("C:\\Users"); - if let Ok(entries) = fs::read_dir(users_dir) { - for entry in entries.flatten() { - let path = entry.path(); - // Ignora pastas de sistema - let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); - if name == "Public" || name == "Default" || name == "Default User" || name == "All Users" { - continue; - } - let rustdesk_config = path.join("AppData").join("Roaming").join("RustDesk").join("config"); - // Verifica se o diretorio pai existe (usuario real) - if path.join("AppData").join("Roaming").exists() { - dirs.push(rustdesk_config); - } - } - } - - // Tambem tenta o APPDATA do ambiente (pode ser util em alguns casos) - if let Ok(appdata) = env::var("APPDATA") { - let path = Path::new(&appdata).join("RustDesk").join("config"); - if !dirs.contains(&path) { - dirs.push(path); - } - } - - dirs -} - -fn service_profile_dirs() -> Vec { - vec![ - PathBuf::from(LOCAL_SERVICE_CONFIG), - PathBuf::from(LOCAL_SYSTEM_CONFIG), - ] -} - -fn remote_id_directories() -> Vec { - let mut dirs = Vec::new(); - dirs.push(program_data_config_dir()); - dirs.extend(service_profile_dirs()); - dirs.extend(all_user_appdata_config_dirs()); - dirs -} - -fn write_config_files() -> Result { - let config_contents = format!( - r#"[options] -key = "{key}" -relay-server = "{host}" -custom-rendezvous-server = "{host}" -api-server = "https://{host}" -verification-method = "{verification}" -approve-mode = "{approve}" -"#, - host = SERVER_HOST, - key = SERVER_KEY, - verification = SECURITY_VERIFICATION_VALUE, - approve = SECURITY_APPROVE_MODE_VALUE, - ); - - let main_path = program_data_config_dir().join("RustDesk2.toml"); - write_file(&main_path, &config_contents)?; - - for service_dir in service_profile_dirs() { - let service_profile = service_dir.join("RustDesk2.toml"); - let _ = write_file(&service_profile, &config_contents); - } - - Ok(main_path) -} - -fn write_file(path: &Path, contents: &str) -> Result<(), io::Error> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(contents.as_bytes()) -} - -fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError> { - run_with_args(exe_path, &["--import-config", &config_path.to_string_lossy()]) -} - -fn set_password(exe_path: &Path, secret: &str) -> Result<(), RustdeskError> { - run_with_args(exe_path, &["--password", secret]) -} - -fn define_custom_id(exe_path: &Path, machine_id: Option<&str>) -> Option { - let value = machine_id.and_then(|raw| { - let trimmed = raw.trim(); - if trimmed.is_empty() { None } else { Some(trimmed) } - })?; - - let custom_id = derive_numeric_id(value); - if run_with_args(exe_path, &["--set-id", &custom_id]).is_ok() { - info!("ID deterministico definido: {}", custom_id); - Some(custom_id) - } else { - None - } -} - -fn derive_numeric_id(machine_id: &str) -> String { - let mut hasher = Sha256::new(); - hasher.update(machine_id.as_bytes()); - let hash = hasher.finalize(); - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&hash[..8]); - let value = u64::from_le_bytes(bytes); - let num = (value % 900_000_000) + 100_000_000; - format!("{:09}", num) -} - -fn ensure_service_running(exe_path: &Path) -> Result<(), RustdeskError> { - ensure_service_installed(exe_path)?; - let _ = run_sc(&["config", SERVICE_NAME, "start=", "auto"]); - let _ = run_sc(&["start", SERVICE_NAME]); - remove_rustdesk_autorun_artifacts(); - Ok(()) -} - -fn ensure_service_installed(exe_path: &Path) -> Result<(), RustdeskError> { - if run_sc(&["query", SERVICE_NAME]).is_ok() { - return Ok(()); - } - run_with_args(exe_path, &["--install-service"]) -} - -fn stop_rustdesk_processes() -> Result<(), RustdeskError> { - let _ = run_sc(&["stop", SERVICE_NAME]); - thread::sleep(Duration::from_secs(2)); - - let status = hidden_command("taskkill") - .args(["/F", "/T", "/IM", "rustdesk.exe"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - - if status.success() || matches!(status.code(), Some(128)) { - Ok(()) - } else { - Err(RustdeskError::CommandFailed { - command: "taskkill".into(), - status: status.code(), - }) - } -} - -fn purge_existing_rustdesk_profiles() -> Result<(), String> { - let files = [ - "RustDesk.toml", - "RustDesk_local.toml", - "RustDesk2.toml", - "password", - "passwd", - "passwd.txt", - ]; - - for dir in remote_id_directories() { - if !dir.exists() { - continue; - } - for name in files { - let path = dir.join(name); - if path.exists() { - let _ = fs::remove_file(&path); - } - } - } - Ok(()) -} - -fn ensure_password_files(secret: &str) -> Result<(), String> { - for dir in remote_id_directories() { - let password_path = dir.join("RustDesk.toml"); - let _ = write_toml_kv(&password_path, "password", secret); - - let local_path = dir.join("RustDesk_local.toml"); - let _ = write_toml_kv(&local_path, "verification-method", SECURITY_VERIFICATION_VALUE); - let _ = write_toml_kv(&local_path, "approve-mode", SECURITY_APPROVE_MODE_VALUE); - } - Ok(()) -} - -fn propagate_password_profile() -> io::Result { - // Encontra um diretorio de usuario que tenha arquivos de config - let user_dirs = all_user_appdata_config_dirs(); - let src_dir = user_dirs.iter().find(|d| d.join("RustDesk.toml").exists()); - - let Some(src_dir) = src_dir else { - // Se nenhum usuario tem config, usa ProgramData como fonte - let pd = program_data_config_dir(); - if !pd.join("RustDesk.toml").exists() { - return Ok(false); - } - return propagate_from_dir(&pd); - }; - - propagate_from_dir(src_dir) -} - -fn propagate_from_dir(src_dir: &Path) -> io::Result { - let propagation_files = ["RustDesk.toml", "RustDesk_local.toml", "RustDesk2.toml"]; - let mut propagated = false; - - for filename in propagation_files { - let src_path = src_dir.join(filename); - if !src_path.exists() { - continue; - } - - for dest_root in remote_id_directories() { - if dest_root == src_dir { - continue; // Nao copiar para si mesmo - } - let target_path = dest_root.join(filename); - if copy_overwrite(&src_path, &target_path).is_ok() { - propagated = true; - } - } - } - - Ok(propagated) -} - -fn ensure_remote_id_files(id: &str) { - for dir in remote_id_directories() { - let path = dir.join("RustDesk_local.toml"); - let _ = write_remote_id_value(&path, id); - } -} - -fn write_remote_id_value(path: &Path, id: &str) -> io::Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let replacement = format!("remote_id = '{}'\n", id); - if let Ok(existing) = fs::read_to_string(path) { - let mut replaced = false; - let mut buffer = String::with_capacity(existing.len() + replacement.len()); - for line in existing.lines() { - if line.trim_start().starts_with("remote_id") { - buffer.push_str(&replacement); - replaced = true; - } else { - buffer.push_str(line); - buffer.push('\n'); - } - } - if !replaced { - buffer.push_str(&replacement); - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(buffer.as_bytes()) - } else { - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(replacement.as_bytes()) - } -} - -fn write_toml_kv(path: &Path, key: &str, value: &str) -> io::Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let sanitized = value.replace('\\', "\\\\").replace('"', "\\\""); - let replacement = format!("{key} = \"{sanitized}\"\n"); - let existing = fs::read_to_string(path).unwrap_or_default(); - let mut replaced = false; - let mut buffer = String::with_capacity(existing.len() + replacement.len()); - for line in existing.lines() { - let trimmed = line.trim_start(); - if trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}=")) { - buffer.push_str(&replacement); - replaced = true; - } else { - buffer.push_str(line); - buffer.push('\n'); - } - } - if !replaced { - buffer.push_str(&replacement); - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(buffer.as_bytes()) -} - -fn read_remote_id_from_profiles() -> Option { - for dir in remote_id_directories() { - for candidate in [dir.join("RustDesk_local.toml"), dir.join("RustDesk.toml")] { - if let Some(id) = read_remote_id_file(&candidate) { - if !id.is_empty() { - return Some(id); - } - } - } - } - None -} - -fn read_remote_id_file(path: &Path) -> Option { - let content = fs::read_to_string(path).ok()?; - for line in content.lines() { - if let Some(value) = parse_assignment(line, "remote_id") { - return Some(value); - } - } - None -} - -fn parse_assignment(line: &str, key: &str) -> Option { - let trimmed = line.trim(); - if !trimmed.starts_with(key) { - return None; - } - let (_, rhs) = trimmed.split_once('=')?; - let value = rhs.trim().trim_matches(|c| c == '\'' || c == '"'); - if value.is_empty() { - None - } else { - Some(value.to_string()) - } -} - -fn query_id_with_retries(exe_path: &Path, attempts: usize) -> Result { - for attempt in 0..attempts { - match query_id(exe_path) { - Ok(value) if !value.trim().is_empty() => return Ok(value), - _ => {} - } - if attempt + 1 < attempts { - thread::sleep(Duration::from_millis(800)); - } - } - Err(RustdeskError::MissingId) -} - -fn query_id(exe_path: &Path) -> Result { - let output = hidden_command(exe_path).arg("--get-id").output()?; - if !output.status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --get-id", exe_path.display()), - status: output.status.code(), - }); - } - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if stdout.is_empty() { - return Err(RustdeskError::MissingId); - } - Ok(stdout) -} - -fn query_version(exe_path: &Path) -> Result { - let output = hidden_command(exe_path).arg("--version").output()?; - if !output.status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --version", exe_path.display()), - status: output.status.code(), - }); - } - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) -} - -fn query_service_state() -> Option { - let output = hidden_command("sc") - .args(["query", SERVICE_NAME]) - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - let lower = line.to_lowercase(); - if lower.contains("running") { - return Some("running".to_string()); - } - if lower.contains("stopped") { - return Some("stopped".to_string()); - } - } - None -} - -fn run_sc(args: &[&str]) -> Result<(), RustdeskError> { - let status = hidden_command("sc") - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("sc {}", args.join(" ")), - status: status.code(), - }); - } - Ok(()) -} - -fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> { - let status = hidden_command(exe_path) - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} {}", exe_path.display(), args.join(" ")), - status: status.code(), - }); - } - Ok(()) -} - -fn remove_rustdesk_autorun_artifacts() { - // Remove atalhos de inicializacao automatica - let mut startup_paths: Vec = Vec::new(); - if let Ok(appdata) = env::var("APPDATA") { - startup_paths.push( - Path::new(&appdata) - .join("Microsoft\\Windows\\Start Menu\\Programs\\Startup\\RustDesk.lnk"), - ); - } - startup_paths.push(PathBuf::from( - r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\RustDesk.lnk", - )); - - for path in startup_paths { - if path.exists() { - let _ = fs::remove_file(&path); - } - } - - // Remove entradas de registro - for hive in ["HKCU", "HKLM"] { - let reg_path = format!(r"{}\Software\Microsoft\Windows\CurrentVersion\Run", hive); - let _ = hidden_command("reg") - .args(["delete", ®_path, "/v", "RustDesk", "/f"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - } -} - -fn ensure_service_profiles_writable() -> Result<(), String> { - for dir in service_profile_dirs() { - if !can_write_dir(&dir) { - fix_profile_acl(&dir)?; - } - } - Ok(()) -} - -fn can_write_dir(dir: &Path) -> bool { - if fs::create_dir_all(dir).is_err() { - return false; - } - let probe = dir.join(".raven_acl_probe"); - match OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&probe) - { - Ok(mut file) => { - if file.write_all(b"ok").is_err() { - let _ = fs::remove_file(&probe); - return false; - } - let _ = fs::remove_file(&probe); - true - } - Err(_) => false, - } -} - -fn fix_profile_acl(target: &Path) -> Result<(), String> { - let target_str = target.display().to_string(); - - // Como ja estamos rodando como LocalSystem, podemos usar takeown/icacls diretamente - let _ = hidden_command("takeown") - .args(["/F", &target_str, "/R", "/D", "Y"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - - let status = hidden_command("icacls") - .args([ - &target_str, - "/grant", - "*S-1-5-32-544:(OI)(CI)F", - "*S-1-5-19:(OI)(CI)F", - "*S-1-5-32-545:(OI)(CI)M", - "/T", - "/C", - "/Q", - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map_err(|e| format!("Erro ao executar icacls: {}", e))?; - - if status.success() { - Ok(()) - } else { - Err(format!("icacls retornou codigo {}", status.code().unwrap_or(-1))) - } -} - -fn copy_overwrite(src: &Path, dst: &Path) -> io::Result<()> { - if let Some(parent) = dst.parent() { - fs::create_dir_all(parent)?; - } - if dst.is_dir() { - fs::remove_dir_all(dst)?; - } else if dst.exists() { - fs::remove_file(dst)?; - } - fs::copy(src, dst)?; - Ok(()) -} - -fn hidden_command(program: impl AsRef) -> Command { - let mut cmd = Command::new(program); - cmd.creation_flags(CREATE_NO_WINDOW); - cmd -} diff --git a/apps/desktop/service/src/usb_policy.rs b/apps/desktop/service/src/usb_policy.rs deleted file mode 100644 index ed8144d..0000000 --- a/apps/desktop/service/src/usb_policy.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! Modulo USB Policy - Controle de dispositivos USB -//! -//! Implementa o controle de armazenamento USB no Windows. -//! Como o servico roda como LocalSystem, nao precisa de elevacao. - -use serde::{Deserialize, Serialize}; -use std::io; -use thiserror::Error; -use tracing::{error, info, warn}; -use winreg::enums::*; -use winreg::RegKey; - -// GUID para Removable Storage Devices (Disk) -const REMOVABLE_STORAGE_GUID: &str = "{53f56307-b6bf-11d0-94f2-00a0c91efb8b}"; - -// Chaves de registro -const REMOVABLE_STORAGE_PATH: &str = r"Software\Policies\Microsoft\Windows\RemovableStorageDevices"; -const USBSTOR_PATH: &str = r"SYSTEM\CurrentControlSet\Services\USBSTOR"; -const STORAGE_POLICY_PATH: &str = r"SYSTEM\CurrentControlSet\Control\StorageDevicePolicies"; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum UsbPolicy { - Allow, - BlockAll, - Readonly, -} - -impl UsbPolicy { - pub fn from_str(s: &str) -> Option { - match s.to_uppercase().as_str() { - "ALLOW" => Some(Self::Allow), - "BLOCK_ALL" => Some(Self::BlockAll), - "READONLY" => Some(Self::Readonly), - _ => None, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - Self::Allow => "ALLOW", - Self::BlockAll => "BLOCK_ALL", - Self::Readonly => "READONLY", - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UsbPolicyResult { - pub success: bool, - pub policy: String, - pub error: Option, - pub applied_at: Option, -} - -#[derive(Error, Debug)] -pub enum UsbControlError { - #[error("Politica USB invalida: {0}")] - InvalidPolicy(String), - - #[error("Erro de registro do Windows: {0}")] - RegistryError(String), - - #[error("Permissao negada")] - PermissionDenied, - - #[error("Erro de I/O: {0}")] - Io(#[from] io::Error), -} - -/// Aplica uma politica de USB -pub fn apply_policy(policy_str: &str) -> Result { - let policy = UsbPolicy::from_str(policy_str) - .ok_or_else(|| UsbControlError::InvalidPolicy(policy_str.to_string()))?; - - let now = chrono::Utc::now().timestamp_millis(); - - info!("Aplicando politica USB: {:?}", policy); - - // 1. Aplicar Removable Storage Policy - apply_removable_storage_policy(policy)?; - - // 2. Aplicar USBSTOR - apply_usbstor_policy(policy)?; - - // 3. Aplicar WriteProtect se necessario - if policy == UsbPolicy::Readonly { - apply_write_protect(true)?; - } else { - apply_write_protect(false)?; - } - - // 4. Atualizar Group Policy (opcional) - if let Err(e) = refresh_group_policy() { - warn!("Falha ao atualizar group policy: {}", e); - } - - info!("Politica USB aplicada com sucesso: {:?}", policy); - - Ok(UsbPolicyResult { - success: true, - policy: policy.as_str().to_string(), - error: None, - applied_at: Some(now), - }) -} - -/// Retorna a politica USB atual -pub fn get_current_policy() -> Result { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - // Verifica Removable Storage Policy primeiro - let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); - - if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_READ) { - let deny_read: u32 = key.get_value("Deny_Read").unwrap_or(0); - let deny_write: u32 = key.get_value("Deny_Write").unwrap_or(0); - - if deny_read == 1 && deny_write == 1 { - return Ok("BLOCK_ALL".to_string()); - } - - if deny_read == 0 && deny_write == 1 { - return Ok("READONLY".to_string()); - } - } - - // Verifica USBSTOR como fallback - if let Ok(key) = hklm.open_subkey_with_flags(USBSTOR_PATH, KEY_READ) { - let start: u32 = key.get_value("Start").unwrap_or(3); - if start == 4 { - return Ok("BLOCK_ALL".to_string()); - } - } - - Ok("ALLOW".to_string()) -} - -fn apply_removable_storage_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); - - match policy { - UsbPolicy::Allow => { - // Tenta remover as restricoes, se existirem - if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_ALL_ACCESS) { - let _ = key.delete_value("Deny_Read"); - let _ = key.delete_value("Deny_Write"); - let _ = key.delete_value("Deny_Execute"); - } - // Tenta remover a chave inteira se estiver vazia - let _ = hklm.delete_subkey(&full_path); - } - UsbPolicy::BlockAll => { - let (key, _) = hklm - .create_subkey(&full_path) - .map_err(map_winreg_error)?; - - key.set_value("Deny_Read", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Write", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Execute", &1u32) - .map_err(map_winreg_error)?; - } - UsbPolicy::Readonly => { - let (key, _) = hklm - .create_subkey(&full_path) - .map_err(map_winreg_error)?; - - // Permite leitura, bloqueia escrita - key.set_value("Deny_Read", &0u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Write", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Execute", &0u32) - .map_err(map_winreg_error)?; - } - } - - Ok(()) -} - -fn apply_usbstor_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - let key = hklm - .open_subkey_with_flags(USBSTOR_PATH, KEY_ALL_ACCESS) - .map_err(map_winreg_error)?; - - match policy { - UsbPolicy::Allow => { - // Start = 3 habilita o driver - key.set_value("Start", &3u32) - .map_err(map_winreg_error)?; - } - UsbPolicy::BlockAll => { - // Start = 4 desabilita o driver - key.set_value("Start", &4u32) - .map_err(map_winreg_error)?; - } - UsbPolicy::Readonly => { - // Readonly mantem driver ativo - key.set_value("Start", &3u32) - .map_err(map_winreg_error)?; - } - } - - Ok(()) -} - -fn apply_write_protect(enable: bool) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - if enable { - let (key, _) = hklm - .create_subkey(STORAGE_POLICY_PATH) - .map_err(map_winreg_error)?; - - key.set_value("WriteProtect", &1u32) - .map_err(map_winreg_error)?; - } else if let Ok(key) = hklm.open_subkey_with_flags(STORAGE_POLICY_PATH, KEY_ALL_ACCESS) { - let _ = key.set_value("WriteProtect", &0u32); - } - - Ok(()) -} - -fn refresh_group_policy() -> Result<(), UsbControlError> { - use std::os::windows::process::CommandExt; - use std::process::Command; - - const CREATE_NO_WINDOW: u32 = 0x08000000; - - let output = Command::new("gpupdate") - .args(["/target:computer", "/force"]) - .creation_flags(CREATE_NO_WINDOW) - .output() - .map_err(UsbControlError::Io)?; - - if !output.status.success() { - warn!( - "gpupdate retornou erro: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - Ok(()) -} - -fn map_winreg_error(error: io::Error) -> UsbControlError { - if let Some(code) = error.raw_os_error() { - if code == 5 { - return UsbControlError::PermissionDenied; - } - } - UsbControlError::RegistryError(error.to_string()) -} diff --git a/apps/desktop/src-tauri/.gitignore b/apps/desktop/src-tauri/.gitignore deleted file mode 100644 index b21bd68..0000000 --- a/apps/desktop/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock deleted file mode 100644 index f5d4b76..0000000 --- a/apps/desktop/src-tauri/Cargo.lock +++ /dev/null @@ -1,6779 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "appsdesktop" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "chrono", - "convex", - "dirs 5.0.1", - "futures-util", - "get_if_addrs", - "hostname", - "once_cell", - "parking_lot", - "reqwest", - "serde", - "serde_json", - "sha2", - "sysinfo", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-plugin-dialog", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-process", - "tauri-plugin-single-instance", - "tauri-plugin-store", - "tauri-plugin-updater", - "thiserror 1.0.69", - "tokio", - "uuid", - "winreg", -] - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "archery" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" - -[[package]] -name = "ashpd" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.2", - "raw-window-handle", - "serde", - "serde_repr", - "tokio", - "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -dependencies = [ - "serde", -] - -[[package]] -name = "bitmaps" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.3", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -dependencies = [ - "serde", -] - -[[package]] -name = "c_linked_list" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.9.4", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.8", -] - -[[package]] -name = "cc" -version = "1.2.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[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.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "convex" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e7ab85cfc76e9a13d252da8a7933ab52f38b9c51de3f7bb8dbe4e2262bac04" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.13.1", - "bytes", - "convex_sync_types", - "futures", - "imbl", - "rand 0.9.2", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "convex_sync_types" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f819ce8fd4370f235f2f5e345499fa219d7d1827cd88923f1fa42942853604a0" -dependencies = [ - "anyhow", - "base64 0.13.1", - "bytes", - "derive_more 2.1.0", - "headers", - "rand 0.9.2", - "serde", - "serde_json", - "strum", - "uuid", -] - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - -[[package]] -name = "deranged" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" -dependencies = [ - "powerfmt", - "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.106", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.106", -] - -[[package]] -name = "derive_more" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" -dependencies = [ - "convert_case 0.10.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.106", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.2", - "libc", - "objc2 0.6.3", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "dlopen2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi 0.3.9", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.8", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" - -[[package]] -name = "flate2" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "get_if_addrs" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7" -dependencies = [ - "c_linked_list", - "get_if_addrs-sys", - "libc", - "winapi 0.2.8", -] - -[[package]] -name = "get_if_addrs-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48" -dependencies = [ - "gcc", - "libc", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi 0.3.9", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.9.4", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64 0.22.1", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hostname" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" -dependencies = [ - "cfg-if", - "libc", - "windows-link 0.1.3", -] - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "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.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "imbl" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4308a675e4cfc1920f36a8f4d8fb62d5533b7da106844bd1ec51c6f1fa94a0c" -dependencies = [ - "archery", - "bitmaps", - "imbl-sized-chunks", - "rand_core 0.9.3", - "rand_xoshiro", - "version_check", -] - -[[package]] -name = "imbl-sized-chunks" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" -dependencies = [ - "bitmaps", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" -dependencies = [ - "equivalent", - "hashbrown 0.16.0", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.9.4", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.11.4", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi 0.3.9", -] - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.4", - "libc", - "redox_syscall", -] - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "mac-notification-sys" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621" -dependencies = [ - "cc", - "objc2 0.6.3", - "objc2-foundation 0.3.2", - "time", -] - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "muda" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.4", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "notify-rust" -version = "4.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" -dependencies = [ - "futures-lite", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.2", - "libc", - "objc2 0.6.3", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.3", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.2", - "libc", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" -dependencies = [ - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-osa-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.2", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-src" -version = "300.5.4+3.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "osakit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" -dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64 0.22.1", - "indexmap 2.11.4", - "quick-xml 0.38.3", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit 0.23.7", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -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.17", - "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.3", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.17", - "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.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "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.3", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "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.3", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_xoshiro" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" -dependencies = [ - "rand_core 0.9.3", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.9.4", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "regex" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "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", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "rfd" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" -dependencies = [ - "ashpd", - "block2 0.6.2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.59.0", -] - -[[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.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rust-ini" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.106", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "indexmap 2.11.4", - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.11.4", - "schemars 0.9.0", - "schemars 1.0.4", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "softbuffer" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" -dependencies = [ - "bytemuck", - "cfg_aliases", - "core-graphics", - "foreign-types 0.5.0", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", - "raw-window-handle", - "redox_syscall", - "wasm-bindgen", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "sysinfo" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows 0.57.0", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-foundation 0.3.2", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs 6.0.0", - "dunce", - "embed_plist", - "getrandom 0.3.3", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-foundation 0.3.2", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.17", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs 6.0.0", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.8", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.106", - "tauri-utils", - "thiserror 2.0.17", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.8", - "walkdir", -] - -[[package]] -name = "tauri-plugin-deep-link" -version = "2.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73" -dependencies = [ - "dunce", - "plist", - "rust-ini", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.17", - "tracing", - "url", - "windows-registry", - "windows-result 0.3.4", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.17", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" -dependencies = [ - "anyhow", - "dunce", - "glob", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.17", - "toml 0.9.8", - "url", -] - -[[package]] -name = "tauri-plugin-notification" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" -dependencies = [ - "log", - "notify-rust", - "rand 0.9.2", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "time", - "url", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation 0.3.2", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "url", - "windows 0.61.3", - "zbus", -] - -[[package]] -name = "tauri-plugin-process" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab" -dependencies = [ - "tauri", - "tauri-plugin", -] - -[[package]] -name = "tauri-plugin-single-instance" -version = "2.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710" -dependencies = [ - "serde", - "serde_json", - "tauri", - "thiserror 2.0.17", - "tracing", - "windows-sys 0.60.2", - "zbus", -] - -[[package]] -name = "tauri-plugin-store" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85dd80d60a76ee2c2fdce09e9ef30877b239c2a6bb76e6d7d03708aa5f13a19" -dependencies = [ - "dunce", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[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 6.0.0", - "flate2", - "futures-util", - "http", - "infer", - "log", - "minisign-verify", - "osakit", - "percent-encoding", - "reqwest", - "semver", - "serde", - "serde_json", - "tar", - "tauri", - "tauri-plugin", - "tempfile", - "thiserror 2.0.17", - "time", - "tokio", - "url", - "windows-sys 0.60.2", - "zip", -] - -[[package]] -name = "tauri-runtime" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2 0.6.3", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-foundation 0.3.2", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.17", - "toml 0.9.8", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" -dependencies = [ - "embed-resource", - "toml 0.9.8", -] - -[[package]] -name = "tauri-winrt-notification" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" -dependencies = [ - "quick-xml 0.37.5", - "thiserror 2.0.17", - "windows 0.61.3", - "windows-version", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "time" -version = "0.3.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "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.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2", - "tokio-macros", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[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-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" -dependencies = [ - "indexmap 2.11.4", - "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", - "toml_parser", - "toml_writer", - "winnow 0.7.13", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.7.3", - "toml_parser", - "winnow 0.7.13", -] - -[[package]] -name = "toml_parser" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" -dependencies = [ - "winnow 0.7.13", -] - -[[package]] -name = "toml_writer" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.4", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" -dependencies = [ - "crossbeam-channel", - "dirs 6.0.0", - "libappindicator", - "muda", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.59.0", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.9.2", - "sha1", - "thiserror 2.0.17", - "url", - "utf-8", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi 0.3.9", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[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.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wayland-backend" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" -dependencies = [ - "cc", - "downcast-rs", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" -dependencies = [ - "bitflags 2.9.4", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" -dependencies = [ - "bitflags 2.9.4", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" -dependencies = [ - "proc-macro2", - "quick-xml 0.37.5", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" -dependencies = [ - "dlib", - "log", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-roots" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement 0.60.2", - "windows-interface 0.59.3", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" -dependencies = [ - "thiserror 2.0.17", - "windows 0.61.3", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "wry" -version = "0.53.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" -dependencies = [ - "base64 0.22.1", - "block2 0.6.2", - "cookie", - "crossbeam-channel", - "dirs 6.0.0", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "nix", - "ordered-stream", - "serde", - "serde_repr", - "tokio", - "tracing", - "uds_windows", - "windows-sys 0.60.2", - "winnow 0.7.13", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.7.13", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zip" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" -dependencies = [ - "arbitrary", - "crc32fast", - "indexmap 2.11.4", - "memchr", -] - -[[package]] -name = "zvariant" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" -dependencies = [ - "endi", - "enumflags2", - "serde", - "url", - "winnow 0.7.13", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.106", - "winnow 0.7.13", -] diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml deleted file mode 100644 index 8e26952..0000000 --- a/apps/desktop/src-tauri/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "appsdesktop" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "appsdesktop_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2.4.1", features = [] } - -[dependencies] -tauri = { version = "2.9", features = ["wry", "devtools", "tray-icon"] } -tauri-plugin-dialog = "2.4.2" -tauri-plugin-opener = "2.5.0" -tauri-plugin-store = "2.4.0" -tauri-plugin-updater = "2.9.0" -tauri-plugin-process = "2.3.0" -tauri-plugin-notification = "2" -tauri-plugin-deep-link = "2" -tauri-plugin-single-instance = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -sysinfo = { version = "0.31", default-features = false, features = ["multithread", "network", "system", "disk"] } -get_if_addrs = "0.5" -reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking", "stream"], default-features = false } -futures-util = "0.3" -tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } -once_cell = "1.19" -thiserror = "1.0" -chrono = { version = "0.4", features = ["serde"] } -parking_lot = "0.12" -hostname = "0.4" -base64 = "0.22" -sha2 = "0.10" -convex = "0.10.2" -uuid = { version = "1", features = ["v4"] } -dirs = "5" -# SSE usa reqwest com stream, nao precisa de websocket - -[target.'cfg(windows)'.dependencies] -winreg = "0.55" diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs deleted file mode 100644 index 0dfb1ae..0000000 --- a/apps/desktop/src-tauri/build.rs +++ /dev/null @@ -1,31 +0,0 @@ -fn main() { - // Custom manifest keeps Common-Controls v6 dependency to avoid TaskDialogIndirect errors. - let windows = tauri_build::WindowsAttributes::new().app_manifest( - r#" - - - - - - - - - - - - - - -"#, - ); - - let attrs = tauri_build::Attributes::new().windows_attributes(windows); - - tauri_build::try_build(attrs).expect("failed to run Tauri build script"); -} diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json deleted file mode 100644 index a0cf79b..0000000 --- a/apps/desktop/src-tauri/capabilities/default.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for all windows", - "windows": ["main", "chat-*", "chat-hub"], - "permissions": [ - "core:default", - "core:event:default", - "core:event:allow-listen", - "core:event:allow-unlisten", - "core:event:allow-emit", - "core:window:default", - "core:window:allow-close", - "core:window:allow-hide", - "core:window:allow-show", - "core:window:allow-set-focus", - "core:window:allow-start-dragging", - "dialog:allow-open", - "opener:default", - "store:default", - "store:allow-load", - "store:allow-set", - "store:allow-get", - "store:allow-save", - "store:allow-delete", - "updater:default", - "process:default", - "notification:default", - "notification:allow-notify", - "notification:allow-request-permission", - "notification:allow-is-permission-granted" - ] -} diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png deleted file mode 100644 index ea1b4de..0000000 Binary files a/apps/desktop/src-tauri/icons/128x128.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png deleted file mode 100644 index 89582e7..0000000 Binary files a/apps/desktop/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png deleted file mode 100644 index 96fd42a..0000000 Binary files a/apps/desktop/src-tauri/icons/32x32.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/64x64.png b/apps/desktop/src-tauri/icons/64x64.png deleted file mode 100644 index bcd4b9c..0000000 Binary files a/apps/desktop/src-tauri/icons/64x64.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Raven.png b/apps/desktop/src-tauri/icons/Raven.png deleted file mode 100644 index ab005ba..0000000 Binary files a/apps/desktop/src-tauri/icons/Raven.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square107x107Logo.png b/apps/desktop/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 0a503d5..0000000 Binary files a/apps/desktop/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square142x142Logo.png b/apps/desktop/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index 31765ac..0000000 Binary files a/apps/desktop/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square150x150Logo.png b/apps/desktop/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index 8a666ce..0000000 Binary files a/apps/desktop/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square284x284Logo.png b/apps/desktop/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index 9bbef05..0000000 Binary files a/apps/desktop/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square30x30Logo.png b/apps/desktop/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index df1c649..0000000 Binary files a/apps/desktop/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square310x310Logo.bmp b/apps/desktop/src-tauri/icons/Square310x310Logo.bmp deleted file mode 100644 index 2aba9f9..0000000 Binary files a/apps/desktop/src-tauri/icons/Square310x310Logo.bmp and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square310x310Logo.png b/apps/desktop/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index c277c69..0000000 Binary files a/apps/desktop/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square44x44Logo.png b/apps/desktop/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index 4608b37..0000000 Binary files a/apps/desktop/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square71x71Logo.png b/apps/desktop/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index e98ccdc..0000000 Binary files a/apps/desktop/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/Square89x89Logo.png b/apps/desktop/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index c0b6c70..0000000 Binary files a/apps/desktop/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/StoreLogo.png b/apps/desktop/src-tauri/icons/StoreLogo.png deleted file mode 100644 index 6b3ea98..0000000 Binary files a/apps/desktop/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon-128.png b/apps/desktop/src-tauri/icons/icon-128.png deleted file mode 100644 index 73fae2a..0000000 Binary files a/apps/desktop/src-tauri/icons/icon-128.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon-256.png b/apps/desktop/src-tauri/icons/icon-256.png deleted file mode 100644 index 598c10f..0000000 Binary files a/apps/desktop/src-tauri/icons/icon-256.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon-32.png b/apps/desktop/src-tauri/icons/icon-32.png deleted file mode 100644 index cfd6dd7..0000000 Binary files a/apps/desktop/src-tauri/icons/icon-32.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon-512.png b/apps/desktop/src-tauri/icons/icon-512.png deleted file mode 100644 index bb67dd8..0000000 Binary files a/apps/desktop/src-tauri/icons/icon-512.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon-64.png b/apps/desktop/src-tauri/icons/icon-64.png deleted file mode 100644 index 5c551e4..0000000 Binary files a/apps/desktop/src-tauri/icons/icon-64.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns deleted file mode 100644 index 9c729c7..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.icns and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico deleted file mode 100644 index 4e2d151..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.ico and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png deleted file mode 100644 index bb67dd8..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/logo-raven-fund-azul.png b/apps/desktop/src-tauri/icons/logo-raven-fund-azul.png deleted file mode 100644 index 9b95662..0000000 Binary files a/apps/desktop/src-tauri/icons/logo-raven-fund-azul.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/logo-raven.png b/apps/desktop/src-tauri/icons/logo-raven.png deleted file mode 100644 index 62b264e..0000000 Binary files a/apps/desktop/src-tauri/icons/logo-raven.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/nsis-header.bmp b/apps/desktop/src-tauri/icons/nsis-header.bmp deleted file mode 100644 index 1313bac..0000000 Binary files a/apps/desktop/src-tauri/icons/nsis-header.bmp and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/nsis-sidebar.bmp b/apps/desktop/src-tauri/icons/nsis-sidebar.bmp deleted file mode 100644 index 061adc8..0000000 Binary files a/apps/desktop/src-tauri/icons/nsis-sidebar.bmp and /dev/null differ diff --git a/apps/desktop/src-tauri/installer-hooks.nsh b/apps/desktop/src-tauri/installer-hooks.nsh deleted file mode 100644 index 72de836..0000000 --- a/apps/desktop/src-tauri/installer-hooks.nsh +++ /dev/null @@ -1,121 +0,0 @@ -; Hooks customizadas do instalador NSIS (Tauri) -; -; Objetivo: -; - Remover a marca "Nullsoft Install System" exibida no canto inferior esquerdo -; - Instalar o Raven Service para operacoes privilegiadas sem UAC -; -; Nota: o bundler do Tauri injeta estes macros no script principal do instalador. - -BrandingText " " - -!macro NSIS_HOOK_PREINSTALL - ; Para e remove qualquer instancia anterior do servico antes de atualizar - DetailPrint "Parando servicos anteriores..." - - ; Para o servico - nsExec::ExecToLog 'sc stop RavenService' - - ; Aguarda o servico parar completamente (ate 10 segundos) - nsExec::ExecToLog 'powershell -Command "$$i=0; while((Get-Service RavenService -ErrorAction SilentlyContinue).Status -eq \"Running\" -and $$i -lt 10){Start-Sleep 1;$$i++}"' - - ; Remove o servico antigo (IMPORTANTE para reinstalacoes) - DetailPrint "Removendo servico antigo..." - IfFileExists "$INSTDIR\raven-service.exe" 0 +2 - nsExec::ExecToLog '"$INSTDIR\raven-service.exe" uninstall' - - ; Fallback: remove via sc delete se o executavel nao existir - nsExec::ExecToLog 'sc delete RavenService' - - ; Forca encerramento de processos remanescentes - nsExec::ExecToLog 'taskkill /F /IM raven-service.exe' - nsExec::ExecToLog 'taskkill /F /IM appsdesktop.exe' - - ; Aguarda liberacao dos arquivos e remocao completa do servico - Sleep 3000 -!macroend - -!macro NSIS_HOOK_POSTINSTALL - ; ========================================================================= - ; Instala e inicia o Raven Service - ; ========================================================================= - - DetailPrint "Instalando Raven Service..." - - ; Garante que nao ha servico residual - nsExec::ExecToLog 'sc delete RavenService' - Sleep 1000 - - ; O servico ja esta em $INSTDIR (copiado como resource pelo Tauri) - ; Registra o servico Windows - nsExec::ExecToLog '"$INSTDIR\raven-service.exe" install' - Pop $0 - - ${If} $0 != 0 - DetailPrint "Aviso: Falha ao registrar servico (codigo: $0)" - ; Tenta remover completamente e reinstalar - nsExec::ExecToLog '"$INSTDIR\raven-service.exe" uninstall' - nsExec::ExecToLog 'sc delete RavenService' - Sleep 1000 - nsExec::ExecToLog '"$INSTDIR\raven-service.exe" install' - Pop $0 - ${EndIf} - - ; Aguarda registro do servico - Sleep 500 - - ; Inicia o servico - DetailPrint "Iniciando Raven Service..." - nsExec::ExecToLog 'sc start RavenService' - Pop $0 - - ${If} $0 == 0 - DetailPrint "Raven Service iniciado com sucesso!" - ${Else} - ; Tenta novamente apos breve espera - Sleep 1000 - nsExec::ExecToLog 'sc start RavenService' - Pop $0 - ${If} $0 == 0 - DetailPrint "Raven Service iniciado com sucesso (segunda tentativa)!" - ${Else} - DetailPrint "Aviso: Servico sera iniciado na proxima reinicializacao (codigo: $0)" - ${EndIf} - ${EndIf} - - ; ========================================================================= - ; Verifica se RustDesk esta instalado - ; Se nao estiver, o Raven Service instalara automaticamente no primeiro uso - ; ========================================================================= - - IfFileExists "$PROGRAMFILES\RustDesk\rustdesk.exe" rustdesk_found rustdesk_not_found - - rustdesk_not_found: - DetailPrint "RustDesk sera instalado automaticamente pelo Raven Service." - Goto rustdesk_done - - rustdesk_found: - DetailPrint "RustDesk ja esta instalado." - - rustdesk_done: -!macroend - -!macro NSIS_HOOK_PREUNINSTALL - ; ========================================================================= - ; Para e remove o Raven Service - ; ========================================================================= - - DetailPrint "Parando Raven Service..." - nsExec::ExecToLog 'sc stop RavenService' - Sleep 1000 - - DetailPrint "Removendo Raven Service..." - nsExec::ExecToLog '"$INSTDIR\raven-service.exe" uninstall' - - ; Aguarda um pouco para garantir que o servico foi removido - Sleep 500 -!macroend - -!macro NSIS_HOOK_POSTUNINSTALL - ; Nada adicional necessario -!macroend - diff --git a/apps/desktop/src-tauri/src/agent.rs b/apps/desktop/src-tauri/src/agent.rs deleted file mode 100644 index 1d42d23..0000000 --- a/apps/desktop/src-tauri/src/agent.rs +++ /dev/null @@ -1,1748 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use chrono::{DateTime, Utc}; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use serde::Serialize; -use serde_json::json; -use std::collections::HashMap; -use sysinfo::{Networks, System}; -use tauri::async_runtime::{self, JoinHandle}; -use tokio::sync::Notify; - -#[derive(thiserror::Error, Debug)] -pub enum AgentError { - #[error("Falha ao obter hostname da dispositivo")] - Hostname, - #[error("Nenhum identificador de hardware disponível (MAC/serial)")] - MissingIdentifiers, - #[error("URL de API inválida")] - InvalidApiUrl, - #[error("Falha HTTP: {0}")] - Http(#[from] reqwest::Error), -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MachineOs { - pub name: String, - pub version: Option, - pub architecture: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MachineMetrics { - pub collected_at: DateTime, - pub cpu_logical_cores: usize, - pub cpu_physical_cores: Option, - pub cpu_usage_percent: f32, - pub load_average_one: Option, - pub load_average_five: Option, - pub load_average_fifteen: Option, - pub memory_total_bytes: u64, - pub memory_used_bytes: u64, - pub memory_used_percent: f32, - pub uptime_seconds: u64, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MachineInventory { - pub cpu_brand: Option, - pub host_identifier: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MachineProfile { - pub hostname: String, - pub os: MachineOs, - pub mac_addresses: Vec, - pub serial_numbers: Vec, - pub inventory: MachineInventory, - pub metrics: MachineMetrics, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -struct HeartbeatPayload { - machine_token: String, - status: Option, - hostname: Option, - os: Option, - metrics: Option, - metadata: Option, -} - -fn collect_mac_addresses() -> Vec { - let mut macs = Vec::new(); - let mut networks = Networks::new(); - networks.refresh_list(); - networks.refresh(); - - for (_, data) in networks.iter() { - let bytes = data.mac_address().0; - if bytes.iter().all(|byte| *byte == 0) { - continue; - } - - let formatted = bytes - .iter() - .map(|byte| format!("{:02x}", byte)) - .collect::>() - .join(":"); - - if !macs.contains(&formatted) { - macs.push(formatted); - } - } - - macs -} - -#[cfg(target_os = "linux")] -fn collect_serials_platform() -> Vec { - let mut out = Vec::new(); - for path in [ - "/sys/class/dmi/id/product_uuid", - "/sys/class/dmi/id/product_serial", - "/sys/class/dmi/id/board_serial", - "/etc/machine-id", - ] { - if let Ok(raw) = std::fs::read_to_string(path) { - let s = raw.trim().to_string(); - if !s.is_empty() && !out.contains(&s) { - out.push(s); - } - } - } - out -} - -#[cfg(any(target_os = "windows", target_os = "macos"))] -fn collect_serials_platform() -> Vec { - // Fase 1: sem coleta nativa; será implementada via WMI/ioreg na fase 2. - Vec::new() -} - -fn collect_serials() -> Vec { - collect_serials_platform() -} - -fn collect_network_addrs() -> Vec { - // Mapa name -> mac via sysinfo (mais estável que get_if_addrs para MAC) - let mut mac_by_name: HashMap = HashMap::new(); - let mut networks = Networks::new(); - networks.refresh_list(); - networks.refresh(); - for (name, data) in networks.iter() { - let bytes = data.mac_address().0; - if bytes.iter().any(|b| *b != 0) { - let mac = bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(":"); - mac_by_name.insert(name.to_string(), mac); - } - } - - let mut entries = Vec::new(); - if let Ok(ifaces) = get_if_addrs::get_if_addrs() { - for iface in ifaces { - let name = iface.name.clone(); - let addr = iface.ip(); - let ip = addr.to_string(); - let mac = mac_by_name.get(&name).cloned(); - entries.push(json!({ - "name": name, - "mac": mac, - "ip": ip, - })); - } - } - entries -} - -fn collect_disks(_system: &System) -> Vec { - let mut out = Vec::new(); - let disks = sysinfo::Disks::new_with_refreshed_list(); - for disk in disks.list() { - let name = disk.name().to_string_lossy().to_string(); - let mount = disk.mount_point().to_string_lossy().to_string(); - let fs = disk.file_system().to_string_lossy().to_string(); - let total = disk.total_space(); - let avail = disk.available_space(); - out.push(json!({ - "name": if name.is_empty() { mount.clone() } else { name }, - "mountPoint": mount, - "fs": fs, - "totalBytes": total, - "availableBytes": avail, - })); - } - - out -} - -fn parse_u64(value: &serde_json::Value) -> Option { - if let Some(num) = value.as_u64() { - return Some(num); - } - if let Some(num) = value.as_f64() { - if num.is_finite() && num >= 0.0 { - return Some(num as u64); - } - } - if let Some(text) = value.as_str() { - if let Ok(parsed) = text.trim().parse::() { - return Some(parsed); - } - } - None -} - -fn push_gpu( - list: &mut Vec, - name: Option<&str>, - memory: Option, - vendor: Option<&str>, - driver: Option<&str>, -) { - if let Some(name) = name { - if name.trim().is_empty() { - return; - } - let mut obj = serde_json::Map::new(); - obj.insert("name".into(), json!(name.trim())); - if let Some(memory) = memory { - obj.insert("memoryBytes".into(), json!(memory)); - } - if let Some(vendor) = vendor { - if !vendor.trim().is_empty() { - obj.insert("vendor".into(), json!(vendor.trim())); - } - } - if let Some(driver) = driver { - if !driver.trim().is_empty() { - obj.insert("driver".into(), json!(driver.trim())); - } - } - list.push(serde_json::Value::Object(obj)); - } -} - -fn build_inventory_metadata(system: &System) -> serde_json::Value { - let cpu_brand = system - .cpus() - .first() - .map(|cpu| cpu.brand().to_string()) - .filter(|brand| !brand.trim().is_empty()); - // sysinfo 0.31 já retorna bytes em total_memory/used_memory - let mem_total_bytes = system.total_memory(); - let network = collect_network_addrs(); - let disks = collect_disks(system); - let mut inventory = json!({ - "cpu": { "brand": cpu_brand.clone() }, - "memory": { "totalBytes": mem_total_bytes }, - "network": network, - "disks": disks, - }); - - if let Some(obj) = inventory.as_object_mut() { - let mut hardware = serde_json::Map::new(); - if let Some(brand) = cpu_brand.clone() { - if !brand.trim().is_empty() { - hardware.insert("cpuType".into(), json!(brand.trim())); - } - } - if let Some(physical) = system.physical_core_count() { - hardware.insert("physicalCores".into(), json!(physical)); - } - hardware.insert("logicalCores".into(), json!(system.cpus().len())); - if mem_total_bytes > 0 { - hardware.insert("memoryBytes".into(), json!(mem_total_bytes)); - hardware.insert("memory".into(), json!(mem_total_bytes)); - } - if !hardware.is_empty() { - obj.insert("hardware".into(), serde_json::Value::Object(hardware)); - } - } - - #[cfg(target_os = "linux")] - { - // Softwares instalados (dpkg ou rpm) - let software = collect_software_linux(); - if let Some(obj) = inventory.as_object_mut() { - obj.insert("software".into(), software); - } - - // Serviços ativos (systemd) - let services = collect_services_linux(); - if let Some(obj) = inventory.as_object_mut() { - obj.insert("services".into(), services); - } - - // Informações estendidas (lsblk/lspci/lsusb/smartctl) - let extended = collect_linux_extended(); - if let Some(obj) = inventory.as_object_mut() { - obj.insert("extended".into(), extended); - } - } - - #[cfg(target_os = "windows")] - { - let mut extended = collect_windows_extended(); - // Fallback: se osInfo vier vazio, preenche com dados do sysinfo - if let Some(win) = extended.get_mut("windows").and_then(|v| v.as_object_mut()) { - let needs_os_info = match win.get("osInfo") { - Some(v) => v.as_object().map(|m| m.is_empty()).unwrap_or(true), - None => true, - }; - if needs_os_info { - let mut osmap = serde_json::Map::new(); - if let Some(name) = System::name() { - osmap.insert("ProductName".into(), json!(name)); - } - if let Some(ver) = System::os_version() { - osmap.insert("Version".into(), json!(ver)); - } - if let Some(build) = System::kernel_version() { - osmap.insert("BuildNumber".into(), json!(build)); - } - win.insert("osInfo".into(), serde_json::Value::Object(osmap)); - } - } - if let Some(obj) = inventory.as_object_mut() { - obj.insert("extended".into(), extended); - } - } - - #[cfg(target_os = "macos")] - { - let extended = collect_macos_extended(); - if let Some(obj) = inventory.as_object_mut() { - obj.insert("extended".into(), extended); - } - } - - // Normalização de software/serviços no topo do inventário - if let Some(obj) = inventory.as_object_mut() { - let extended_snapshot = obj.get("extended").and_then(|v| v.as_object()).cloned(); - // Merge software - let mut software: Vec = Vec::new(); - if let Some(existing) = obj.get("software").and_then(|v| v.as_array()) { - software.extend(existing.iter().cloned()); - } - if let Some(ext) = extended_snapshot.as_ref() { - // Windows normalize - if let Some(win) = ext.get("windows").and_then(|v| v.as_object()) { - if let Some(ws) = win.get("software").and_then(|v| v.as_array()) { - for item in ws { - let name = item - .get("DisplayName") - .or_else(|| item.get("name")) - .cloned() - .unwrap_or(json!(null)); - let version = item - .get("DisplayVersion") - .or_else(|| item.get("version")) - .cloned() - .unwrap_or(json!(null)); - let publisher = item.get("Publisher").cloned().unwrap_or(json!(null)); - software - .push(json!({ "name": name, "version": version, "source": publisher })); - } - } - } - // macOS normalize - if let Some(macos) = ext.get("macos").and_then(|v| v.as_object()) { - if let Some(pkgs) = macos.get("packages").and_then(|v| v.as_array()) { - for p in pkgs { - software.push(json!({ "name": p, "version": null, "source": "pkgutil" })); - } - } - } - } - if !software.is_empty() { - obj.insert("software".into(), json!(software)); - } - - // Merge services - let mut services: Vec = Vec::new(); - if let Some(existing) = obj.get("services").and_then(|v| v.as_array()) { - services.extend(existing.iter().cloned()); - } - if let Some(ext) = extended_snapshot.as_ref() { - if let Some(win) = ext.get("windows").and_then(|v| v.as_object()) { - if let Some(wsvc) = win.get("services").and_then(|v| v.as_array()) { - for s in wsvc { - let name = s.get("Name").cloned().unwrap_or(json!(null)); - let status = s.get("Status").cloned().unwrap_or(json!(null)); - let display = s.get("DisplayName").cloned().unwrap_or(json!(null)); - services.push( - json!({ "name": name, "status": status, "displayName": display }), - ); - } - } - } - } - if !services.is_empty() { - obj.insert("services".into(), json!(services)); - } - - let mut gpus: Vec = Vec::new(); - if let Some(ext) = extended_snapshot.as_ref() { - if let Some(win) = ext.get("windows").and_then(|v| v.as_object()) { - if let Some(video) = win.get("videoControllers").and_then(|v| v.as_array()) { - for controller in video { - let name = controller.get("Name").and_then(|v| v.as_str()); - let memory = controller.get("AdapterRAM").and_then(parse_u64); - let driver = controller.get("DriverVersion").and_then(|v| v.as_str()); - push_gpu(&mut gpus, name, memory, None, driver); - } - } - if obj - .get("disks") - .and_then(|v| v.as_array()) - .map(|arr| arr.is_empty()) - .unwrap_or(true) - { - if let Some(raw) = win.get("disks").and_then(|v| v.as_array()) { - let mapped = raw - .iter() - .map(|disk| { - let model = disk.get("Model").and_then(|v| v.as_str()).unwrap_or_default(); - let serial = disk.get("SerialNumber").and_then(|v| v.as_str()).unwrap_or_default(); - let size = parse_u64(disk.get("Size").unwrap_or(&serde_json::Value::Null)).unwrap_or(0); - let interface = disk.get("InterfaceType").and_then(|v| v.as_str()).unwrap_or(""); - let media = disk.get("MediaType").and_then(|v| v.as_str()).unwrap_or(""); - json!({ - "name": if !model.is_empty() { model } else { serial }, - "mountPoint": "", - "fs": if !media.is_empty() { media } else { "—" }, - "interface": if !interface.is_empty() { serde_json::Value::String(interface.to_string()) } else { serde_json::Value::Null }, - "serial": if !serial.is_empty() { serde_json::Value::String(serial.to_string()) } else { serde_json::Value::Null }, - "totalBytes": size, - "availableBytes": serde_json::Value::Null, - }) - }) - .collect::>(); - if !mapped.is_empty() { - obj.insert("disks".into(), json!(mapped)); - } - } - } - } - if let Some(linux) = ext.get("linux").and_then(|v| v.as_object()) { - if let Some(pci) = linux.get("pciList").and_then(|v| v.as_array()) { - for entry in pci { - if let Some(text) = entry.get("text").and_then(|v| v.as_str()) { - let lower = text.to_lowercase(); - if lower.contains(" vga ") - || lower.contains(" 3d controller") - || lower.contains("display controller") - { - push_gpu(&mut gpus, Some(text), None, None, None); - } - } - } - } - } - if let Some(macos) = ext.get("macos").and_then(|v| v.as_object()) { - if let Some(profiler) = macos.get("systemProfiler").and_then(|v| v.as_object()) { - if let Some(displays) = profiler - .get("SPDisplaysDataType") - .and_then(|v| v.as_array()) - { - for display in displays { - if let Some(d) = display.as_object() { - let name = d.get("_name").and_then(|v| v.as_str()); - let vram = d - .get("spdisplays_vram") - .and_then(|v| v.as_str()) - .and_then(|s| { - let digits = s.split_whitespace().next().unwrap_or(""); - digits.parse::().ok().map(|n| { - if s.to_lowercase().contains("gb") { - n * 1024 * 1024 * 1024 - } else if s.to_lowercase().contains("mb") { - n * 1024 * 1024 - } else { - n - } - }) - }); - push_gpu(&mut gpus, name, vram, None, None); - } - } - } - } - } - } - - if !gpus.is_empty() { - let entry = obj.entry("hardware").or_insert_with(|| json!({})); - if let Some(hardware) = entry.as_object_mut() { - hardware.insert("gpus".into(), json!(gpus.clone())); - if let Some(primary) = gpus.first() { - hardware.insert("primaryGpu".into(), primary.clone()); - } - } - } - } - - json!({ "inventory": inventory }) -} - -pub fn collect_inventory_plain() -> serde_json::Value { - let system = collect_system(); - let meta = build_inventory_metadata(&system); - match meta.get("inventory") { - Some(value) => value.clone(), - None => json!({}), - } -} - -#[cfg(target_os = "linux")] -fn collect_software_linux() -> serde_json::Value { - use std::process::Command; - // Tenta dpkg-query primeiro - let dpkg = Command::new("sh") - .arg("-lc") - .arg("dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null || true") - .output(); - if let Ok(out) = dpkg { - if out.status.success() { - let s = String::from_utf8_lossy(&out.stdout); - let mut items = Vec::new(); - for line in s.lines() { - let mut parts = line.split('\t'); - let name = parts.next().unwrap_or("").trim(); - let version = parts.next().unwrap_or("").trim(); - if !name.is_empty() { - items.push(json!({"name": name, "version": version, "source": "dpkg"})); - } - } - return json!(items); - } - } - - // Fallback rpm - let rpm = std::process::Command::new("sh") - .arg("-lc") - .arg("rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\n' 2>/dev/null || true") - .output(); - if let Ok(out) = rpm { - if out.status.success() { - let s = String::from_utf8_lossy(&out.stdout); - let mut items = Vec::new(); - for line in s.lines() { - let mut parts = line.split('\t'); - let name = parts.next().unwrap_or("").trim(); - let version = parts.next().unwrap_or("").trim(); - if !name.is_empty() { - items.push(json!({"name": name, "version": version, "source": "rpm"})); - } - } - return json!(items); - } - } - json!([]) -} - -#[cfg(target_os = "linux")] -fn collect_services_linux() -> serde_json::Value { - use std::process::Command; - let out = Command::new("sh") - .arg("-lc") - .arg("systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null || true") - .output(); - if let Ok(out) = out { - if out.status.success() { - let s = String::from_utf8_lossy(&out.stdout); - let mut items = Vec::new(); - for line in s.lines() { - // Typical format: UNIT LOAD ACTIVE SUB DESCRIPTION - // We take UNIT and ACTIVE - let cols: Vec<&str> = line.split_whitespace().collect(); - if cols.is_empty() { - continue; - } - let unit = cols.get(0).unwrap_or(&""); - let active = cols.get(2).copied().unwrap_or(""); - if !unit.is_empty() { - items.push(json!({"name": unit, "status": active})); - } - } - return json!(items); - } - } - json!([]) -} - -#[cfg(target_os = "linux")] -fn collect_linux_extended() -> serde_json::Value { - use std::process::Command; - // lsblk em JSON (block devices) - let block_json = Command::new("sh") - .arg("-lc") - .arg("lsblk -J -b 2>/dev/null || true") - .output() - .ok() - .and_then(|out| { - if out.status.success() { - Some(out.stdout) - } else { - Some(out.stdout) - } - }) - .and_then(|bytes| serde_json::from_slice::(&bytes).ok()) - .unwrap_or_else(|| json!({})); - - // lspci e lsusb — texto livre (depende de pacotes pciutils/usbutils) - let lspci = Command::new("sh") - .arg("-lc") - .arg("lspci 2>/dev/null || true") - .output() - .ok() - .map(|out| String::from_utf8_lossy(&out.stdout).to_string()) - .unwrap_or_default(); - let lsusb = Command::new("sh") - .arg("-lc") - .arg("lsusb 2>/dev/null || true") - .output() - .ok() - .map(|out| String::from_utf8_lossy(&out.stdout).to_string()) - .unwrap_or_default(); - // Parse básico de lspci/lsusb em listas - fn parse_lines_to_list(input: &str) -> Vec { - input - .lines() - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .map(|l| json!({ "text": l })) - .collect::>() - } - let pci_list = parse_lines_to_list(&lspci); - let usb_list = parse_lines_to_list(&lsusb); - - // smartctl (se disponível) por disco - let mut smart: Vec = Vec::new(); - if let Ok(out) = std::process::Command::new("sh") - .arg("-lc") - .arg("command -v smartctl >/dev/null 2>&1 && echo yes || echo no") - .output() - { - if out.status.success() && String::from_utf8_lossy(&out.stdout).contains("yes") { - if let Some(devices) = block_json.get("blockdevices").and_then(|v| v.as_array()) { - for dev in devices { - let t = dev.get("type").and_then(|v| v.as_str()).unwrap_or(""); - let name = dev.get("name").and_then(|v| v.as_str()).unwrap_or(""); - if t == "disk" && !name.is_empty() { - let path = format!("/dev/{}", name); - if let Ok(out) = Command::new("sh") - .arg("-lc") - .arg(format!("smartctl -H -j {} 2>/dev/null || true", path)) - .output() - { - if out.status.success() || !out.stdout.is_empty() { - if let Ok(val) = - serde_json::from_slice::(&out.stdout) - { - smart.push(val); - } - } - } - } - } - } - } - } - - json!({ - "linux": { - "lsblk": block_json, - "lspci": lspci, - "lsusb": lsusb, - "pciList": pci_list, - "usbList": usb_list, - "smart": smart, - } - }) -} - -#[cfg(target_os = "windows")] -fn collect_windows_extended() -> serde_json::Value { - use base64::engine::general_purpose::STANDARD; - use base64::Engine as _; - use std::os::windows::process::CommandExt; - use std::process::Command; - const CREATE_NO_WINDOW: u32 = 0x08000000; - - fn decode_powershell_text(bytes: &[u8]) -> Option { - if bytes.is_empty() { - return None; - } - if bytes.starts_with(&[0xFF, 0xFE]) { - return decode_utf16_le_to_string(&bytes[2..]); - } - if bytes.len() >= 2 && bytes[1] == 0 { - if let Some(s) = decode_utf16_le_to_string(bytes) { - return Some(s); - } - } - if bytes.contains(&0) { - if let Some(s) = decode_utf16_le_to_string(bytes) { - return Some(s); - } - } - let text = std::str::from_utf8(bytes).ok()?.trim().to_string(); - if text.is_empty() { - None - } else { - Some(text) - } - } - - fn decode_utf16_le_to_string(bytes: &[u8]) -> Option { - if !bytes.len().is_multiple_of(2) { - return None; - } - let utf16: Vec = bytes - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect(); - let text = String::from_utf16(&utf16).ok()?; - let trimmed = text.trim(); - if trimmed.is_empty() { - None - } else { - Some(trimmed.to_string()) - } - } - - fn preview_base64(bytes: &[u8], max_len: usize) -> String { - if bytes.is_empty() { - return "".to_string(); - } - let prefix = if bytes.len() > max_len { - &bytes[..max_len] - } else { - bytes - }; - format!("base64:{}...", STANDARD.encode(prefix)) - } - - fn encode_ps_script(script: &str) -> String { - let mut bytes = Vec::with_capacity(script.len() * 2); - for unit in script.encode_utf16() { - bytes.extend_from_slice(&unit.to_le_bytes()); - } - STANDARD.encode(bytes) - } - - fn ps(cmd: &str) -> Option { - let script = format!( - "$ErrorActionPreference='SilentlyContinue';$ProgressPreference='SilentlyContinue';$result = & {{\n{}\n}};if ($null -eq $result) {{ return }};$json = $result | ConvertTo-Json -Depth 4 -Compress;if ([string]::IsNullOrWhiteSpace($json)) {{ return }};[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$json;", - cmd - ); - let encoded = encode_ps_script(&script); - let out = Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .arg("-NoProfile") - .arg("-NoLogo") - .arg("-NonInteractive") - .arg("-ExecutionPolicy") - .arg("Bypass") - .arg("-EncodedCommand") - .arg(encoded) - .output() - .ok()?; - let stdout_text = decode_powershell_text(&out.stdout); - if cfg!(test) { - if let Some(ref txt) = stdout_text { - let preview = txt.chars().take(512).collect::(); - eprintln!("[collect_windows_extended] stdout `{cmd}` => {preview}"); - } else { - let preview = preview_base64(&out.stdout, 512); - eprintln!( - "[collect_windows_extended] stdout `{cmd}` => " - ); - } - if !out.stderr.is_empty() { - if let Some(err) = decode_powershell_text(&out.stderr) { - eprintln!("[collect_windows_extended] stderr `{cmd}` => {err}"); - } else { - let preview = preview_base64(&out.stderr, 512); - eprintln!( - "[collect_windows_extended] stderr `{cmd}` => " - ); - } - } - } - stdout_text.and_then(|text| serde_json::from_str::(&text).ok()) - } - - let software = ps(r#"@(Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'; Get-ItemProperty 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') | Where-Object { $_.DisplayName } | Select-Object DisplayName, DisplayVersion, Publisher"#) - .unwrap_or_else(|| json!([])); - let services = - ps("@(Get-Service | Select-Object Name,Status,DisplayName)").unwrap_or_else(|| json!([])); - let defender = ps("Get-MpComputerStatus | Select-Object AMRunningMode,AntivirusEnabled,RealTimeProtectionEnabled,AntispywareEnabled").unwrap_or_else(|| json!({})); - let hotfix = ps("Get-HotFix | Select-Object HotFixID,InstalledOn").unwrap_or_else(|| json!([])); - let bitlocker = ps( - "@(if (Get-Command -Name Get-BitLockerVolume -ErrorAction SilentlyContinue) { Get-BitLockerVolume | Select-Object MountPoint,VolumeStatus,ProtectionStatus,LockStatus,EncryptionMethod,EncryptionPercentage,CapacityGB,KeyProtector } else { @() })", - ) - .unwrap_or_else(|| json!([])); - let tpm = ps( - "if (Get-Command -Name Get-Tpm -ErrorAction SilentlyContinue) { Get-Tpm | Select-Object TpmPresent,TpmReady,TpmEnabled,TpmActivated,ManagedAuthLevel,OwnerAuth,ManufacturerId,ManufacturerIdTxt,ManufacturerVersion,ManufacturerVersionFull20,SpecVersion } else { $null }", - ) - .unwrap_or_else(|| json!({})); - let secure_boot = ps( - r#" - if (-not (Get-Command -Name Confirm-SecureBootUEFI -ErrorAction SilentlyContinue)) { - [PSCustomObject]@{ Supported = $false; Enabled = $null; Error = 'Cmdlet Confirm-SecureBootUEFI indisponível' } - } else { - try { - $enabled = Confirm-SecureBootUEFI - [PSCustomObject]@{ Supported = $true; Enabled = [bool]$enabled; Error = $null } - } catch { - [PSCustomObject]@{ Supported = $true; Enabled = $null; Error = $_.Exception.Message } - } - } - "#, - ) - .unwrap_or_else(|| json!({})); - let device_guard = ps( - "@(Get-CimInstance -ClassName Win32_DeviceGuard | Select-Object SecurityServicesConfigured,SecurityServicesRunning,RequiredSecurityProperties,AvailableSecurityProperties,VirtualizationBasedSecurityStatus)", - ) - .unwrap_or_else(|| json!([])); - let firewall_profiles = ps( - "@(Get-NetFirewallProfile | Select-Object Name,Enabled,DefaultInboundAction,DefaultOutboundAction,NotifyOnListen)", - ) - .unwrap_or_else(|| json!([])); - let windows_update = ps( - r#" - $reg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -ErrorAction SilentlyContinue - if ($null -eq $reg) { return $null } - $last = $null - if ($reg.PSObject.Properties.Name -contains 'LastSuccessTime') { - $raw = $reg.LastSuccessTime - if ($raw) { - try { - if ($raw -is [DateTime]) { - $last = ($raw.ToUniversalTime()).ToString('o') - } elseif ($raw -is [string]) { - $last = $raw - } else { - $last = [DateTime]::FromFileTimeUtc([long]$raw).ToString('o') - } - } catch { - $last = $raw - } - } - } - [PSCustomObject]@{ - AUOptions = $reg.AUOptions - NoAutoUpdate = $reg.NoAutoUpdate - ScheduledInstallDay = $reg.ScheduledInstallDay - ScheduledInstallTime = $reg.ScheduledInstallTime - DetectionFrequency = $reg.DetectionFrequencyEnabled - LastSuccessTime = $last - } - "#, - ) - .unwrap_or_else(|| json!({})); - let computer_system = ps( - "Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer,Model,Domain,DomainRole,PartOfDomain,Workgroup,TotalPhysicalMemory,HypervisorPresent,PCSystemType,PCSystemTypeEx", - ) - .unwrap_or_else(|| json!({})); - let device_join = ps( - r#" - $output = & dsregcmd.exe /status 2>$null - if (-not $output) { return $null } - $map = [ordered]@{} - $current = $null - foreach ($line in $output) { - if ([string]::IsNullOrWhiteSpace($line)) { continue } - if ($line -match '^\[(.+)\]$') { - $current = $matches[1].Trim() - if (-not $map.Contains($current)) { - $map[$current] = [ordered]@{} - } - continue - } - if (-not $current) { continue } - $parts = $line.Split(':', 2) - if ($parts.Length -ne 2) { continue } - $key = $parts[0].Trim() - $value = $parts[1].Trim() - if ($key) { - ($map[$current])[$key] = $value - } - } - if ($map.Count -eq 0) { return $null } - $obj = [ordered]@{} - foreach ($entry in $map.GetEnumerator()) { - $obj[$entry.Key] = [PSCustomObject]$entry.Value - } - [PSCustomObject]$obj - "#, - ) - .unwrap_or_else(|| json!({})); - - // Informações de build/edição e ativação - let os_info = ps(r#" - $cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'; - $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue; - $lsItems = Get-CimInstance -Query "SELECT Name, LicenseStatus, PartialProductKey FROM SoftwareLicensingProduct WHERE PartialProductKey IS NOT NULL" | Where-Object { $_.Name -like 'Windows*' }; - $activatedItem = $lsItems | Where-Object { $_.LicenseStatus -eq 1 } | Select-Object -First 1; - $primaryItem = if ($activatedItem) { $activatedItem } else { $lsItems | Select-Object -First 1 }; - $lsCode = if ($primaryItem -and $primaryItem.LicenseStatus -ne $null) { [int]$primaryItem.LicenseStatus } else { 0 }; - [PSCustomObject]@{ - ProductName = $cv.ProductName - CurrentBuild = $cv.CurrentBuild - CurrentBuildNumber = $cv.CurrentBuildNumber - DisplayVersion = $cv.DisplayVersion - ReleaseId = $cv.ReleaseId - EditionID = $cv.EditionID - UBR = $cv.UBR - CompositionEditionID = $cv.CompositionEditionID - InstallationType = $cv.InstallationType - InstallDate = $cv.InstallDate - InstallationDate = $os.InstallDate - InstalledOn = $os.InstallDate - Version = $os.Version - BuildNumber = $os.BuildNumber - Caption = $os.Caption - FeatureExperiencePack = $cv.FeatureExperiencePack - LicenseStatus = $lsCode - IsActivated = ($activatedItem -ne $null) - } - "#).unwrap_or_else(|| json!({})); - - // Hardware detalhado (CPU/Board/BIOS/Memória/Vídeo/Discos) - let cpu = ps("Get-CimInstance Win32_Processor | Select-Object Name,Manufacturer,SocketDesignation,NumberOfCores,NumberOfLogicalProcessors,L2CacheSize,L3CacheSize,MaxClockSpeed").unwrap_or_else(|| json!({})); - let baseboard = ps( - "Get-CimInstance Win32_BaseBoard | Select-Object Product,Manufacturer,SerialNumber,Version", - ) - .unwrap_or_else(|| json!({})); - let bios = ps("Get-CimInstance Win32_BIOS | Select-Object Manufacturer,SMBIOSBIOSVersion,ReleaseDate,Version").unwrap_or_else(|| json!({})); - let memory = ps("@(Get-CimInstance Win32_PhysicalMemory | Select-Object BankLabel,Capacity,Manufacturer,PartNumber,SerialNumber,ConfiguredClockSpeed,Speed,ConfiguredVoltage)").unwrap_or_else(|| json!([])); - // Coleta de GPU com VRAM correta (nvidia-smi para NVIDIA, registro como fallback para >4GB) - let video = ps(r#" - $gpus = @() - $wmiGpus = Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM,DriverVersion,PNPDeviceID - foreach ($gpu in $wmiGpus) { - $vram = $gpu.AdapterRAM - # Tenta nvidia-smi para GPUs NVIDIA (retorna valor correto para >4GB) - if ($gpu.Name -match 'NVIDIA') { - try { - $nvidiaSmi = & 'nvidia-smi' '--query-gpu=memory.total' '--format=csv,noheader,nounits' 2>$null - if ($nvidiaSmi) { - $vramMB = [int64]($nvidiaSmi.Trim()) - $vram = $vramMB * 1024 * 1024 - } - } catch {} - } - # Fallback: tenta registro do Windows (qwMemorySize é uint64) - if ($vram -le 4294967296 -and $vram -gt 0) { - try { - $regPath = 'HKLM:\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0*' - $regGpus = Get-ItemProperty $regPath -ErrorAction SilentlyContinue - foreach ($reg in $regGpus) { - if ($reg.DriverDesc -eq $gpu.Name -and $reg.'HardwareInformation.qwMemorySize') { - $vram = [int64]$reg.'HardwareInformation.qwMemorySize' - break - } - } - } catch {} - } - $gpus += [PSCustomObject]@{ - Name = $gpu.Name - AdapterRAM = $vram - DriverVersion = $gpu.DriverVersion - PNPDeviceID = $gpu.PNPDeviceID - } - } - @($gpus) - "#).unwrap_or_else(|| json!([])); - let disks = ps("@(Get-CimInstance Win32_DiskDrive | Select-Object Model,SerialNumber,Size,InterfaceType,MediaType)").unwrap_or_else(|| json!([])); - - // Bateria (notebooks/laptops) - let battery = ps(r#" - $batteries = @(Get-CimInstance Win32_Battery | Select-Object Name,DeviceID,Status,BatteryStatus,EstimatedChargeRemaining,EstimatedRunTime,DesignCapacity,FullChargeCapacity,DesignVoltage,Chemistry,BatteryRechargeTime) - if ($batteries.Count -eq 0) { - [PSCustomObject]@{ Present = $false; Batteries = @() } - } else { - # Mapeia status numérico para texto - $statusMap = @{ - 1 = 'Discharging' - 2 = 'AC Power' - 3 = 'Fully Charged' - 4 = 'Low' - 5 = 'Critical' - 6 = 'Charging' - 7 = 'Charging High' - 8 = 'Charging Low' - 9 = 'Charging Critical' - 10 = 'Undefined' - 11 = 'Partially Charged' - } - foreach ($b in $batteries) { - if ($b.BatteryStatus) { - $b | Add-Member -NotePropertyName 'BatteryStatusText' -NotePropertyValue ($statusMap[[int]$b.BatteryStatus] ?? 'Unknown') -Force - } - } - [PSCustomObject]@{ Present = $true; Batteries = $batteries } - } - "#).unwrap_or_else(|| json!({ "Present": false, "Batteries": [] })); - - // Sensores térmicos (temperatura CPU/GPU quando disponível) - let thermal = ps(r#" - $temps = @() - # Tenta WMI thermal zone (requer admin em alguns sistemas) - try { - $zones = Get-CimInstance -Namespace 'root/WMI' -ClassName MSAcpi_ThermalZoneTemperature -ErrorAction SilentlyContinue - foreach ($z in $zones) { - if ($z.CurrentTemperature) { - $celsius = [math]::Round(($z.CurrentTemperature - 2732) / 10, 1) - $temps += [PSCustomObject]@{ - Source = 'ThermalZone' - Name = $z.InstanceName - TemperatureCelsius = $celsius - CriticalTripPoint = if ($z.CriticalTripPoint) { [math]::Round(($z.CriticalTripPoint - 2732) / 10, 1) } else { $null } - } - } - } - } catch {} - # CPU temp via Open Hardware Monitor WMI (se instalado) - try { - $ohm = Get-CimInstance -Namespace 'root/OpenHardwareMonitor' -ClassName Sensor -ErrorAction SilentlyContinue | Where-Object { $_.SensorType -eq 'Temperature' } - foreach ($s in $ohm) { - $temps += [PSCustomObject]@{ - Source = 'OpenHardwareMonitor' - Name = $s.Name - TemperatureCelsius = $s.Value - Parent = $s.Parent - } - } - } catch {} - @($temps) - "#).unwrap_or_else(|| json!([])); - - // Adaptadores de rede (físicos e virtuais) - let network_adapters = ps(r#" - @(Get-CimInstance Win32_NetworkAdapter | Where-Object { $_.PhysicalAdapter -eq $true -or $_.NetConnectionStatus -ne $null } | Select-Object Name,Description,MACAddress,Speed,NetConnectionStatus,AdapterType,Manufacturer,NetConnectionID,PNPDeviceID | ForEach-Object { - $statusMap = @{ - 0 = 'Disconnected' - 1 = 'Connecting' - 2 = 'Connected' - 3 = 'Disconnecting' - 4 = 'Hardware not present' - 5 = 'Hardware disabled' - 6 = 'Hardware malfunction' - 7 = 'Media disconnected' - 8 = 'Authenticating' - 9 = 'Authentication succeeded' - 10 = 'Authentication failed' - 11 = 'Invalid address' - 12 = 'Credentials required' - } - $_ | Add-Member -NotePropertyName 'StatusText' -NotePropertyValue ($statusMap[[int]$_.NetConnectionStatus] ?? 'Unknown') -Force - $_ - }) - "#).unwrap_or_else(|| json!([])); - - // Monitores conectados - let monitors = ps(r#" - @(Get-CimInstance WmiMonitorID -Namespace root/wmi -ErrorAction SilentlyContinue | ForEach-Object { - $decode = { param($arr) if ($arr) { -join ($arr | Where-Object { $_ -ne 0 } | ForEach-Object { [char]$_ }) } else { $null } } - [PSCustomObject]@{ - ManufacturerName = & $decode $_.ManufacturerName - ProductCodeID = & $decode $_.ProductCodeID - SerialNumberID = & $decode $_.SerialNumberID - UserFriendlyName = & $decode $_.UserFriendlyName - YearOfManufacture = $_.YearOfManufacture - WeekOfManufacture = $_.WeekOfManufacture - } - }) - "#).unwrap_or_else(|| json!([])); - - // Fonte de alimentação / chassis - let power_supply = ps(r#" - $chassis = Get-CimInstance Win32_SystemEnclosure | Select-Object ChassisTypes,Manufacturer,SerialNumber,SMBIOSAssetTag - $chassisTypeMap = @{ - 1 = 'Other'; 2 = 'Unknown'; 3 = 'Desktop'; 4 = 'Low Profile Desktop' - 5 = 'Pizza Box'; 6 = 'Mini Tower'; 7 = 'Tower'; 8 = 'Portable' - 9 = 'Laptop'; 10 = 'Notebook'; 11 = 'Hand Held'; 12 = 'Docking Station' - 13 = 'All in One'; 14 = 'Sub Notebook'; 15 = 'Space-Saving'; 16 = 'Lunch Box' - 17 = 'Main Server Chassis'; 18 = 'Expansion Chassis'; 19 = 'SubChassis' - 20 = 'Bus Expansion Chassis'; 21 = 'Peripheral Chassis'; 22 = 'RAID Chassis' - 23 = 'Rack Mount Chassis'; 24 = 'Sealed-case PC'; 25 = 'Multi-system chassis' - 30 = 'Tablet'; 31 = 'Convertible'; 32 = 'Detachable' - } - $types = @() - if ($chassis.ChassisTypes) { - foreach ($t in $chassis.ChassisTypes) { - $types += $chassisTypeMap[[int]$t] ?? "Type$t" - } - } - [PSCustomObject]@{ - ChassisTypes = $chassis.ChassisTypes - ChassisTypesText = $types - Manufacturer = $chassis.Manufacturer - SerialNumber = $chassis.SerialNumber - SMBIOSAssetTag = $chassis.SMBIOSAssetTag - } - "#).unwrap_or_else(|| json!({})); - - // Último reinício e contagem de boots - let boot_info = ps(r#" - $os = Get-CimInstance Win32_OperatingSystem | Select-Object LastBootUpTime - $lastBoot = $os.LastBootUpTime - - # Calcula uptime - $uptime = if ($lastBoot) { (New-TimeSpan -Start $lastBoot -End (Get-Date)).TotalSeconds } else { 0 } - - # Conta eventos de boot (ID 6005) - últimos 30 dias para performance - $startDate = (Get-Date).AddDays(-30) - $bootEvents = @() - $bootCount = 0 - try { - $events = Get-WinEvent -FilterHashtable @{ - LogName = 'System' - ID = 6005 - StartTime = $startDate - } -MaxEvents 50 -ErrorAction SilentlyContinue - $bootCount = @($events).Count - $bootEvents = @($events | Select-Object -First 10 | ForEach-Object { - @{ - TimeCreated = $_.TimeCreated.ToString('o') - Computer = $_.MachineName - } - }) - } catch {} - - [PSCustomObject]@{ - LastBootTime = if ($lastBoot) { $lastBoot.ToString('o') } else { $null } - UptimeSeconds = [math]::Round($uptime) - BootCountLast30Days = $bootCount - RecentBoots = $bootEvents - } - "#).unwrap_or_else(|| json!({ "LastBootTime": null, "UptimeSeconds": 0, "BootCountLast30Days": 0, "RecentBoots": [] })); - - json!({ - "windows": { - "software": software, - "services": services, - "defender": defender, - "hotfix": hotfix, - "osInfo": os_info, - "cpu": cpu, - "baseboard": baseboard, - "bios": bios, - "memoryModules": memory, - "videoControllers": video, - "disks": disks, - "bitLocker": bitlocker, - "tpm": tpm, - "secureBoot": secure_boot, - "deviceGuard": device_guard, - "firewallProfiles": firewall_profiles, - "windowsUpdate": windows_update, - "computerSystem": computer_system, - "azureAdStatus": device_join, - "battery": battery, - "thermal": thermal, - "networkAdapters": network_adapters, - "monitors": monitors, - "chassis": power_supply, - "bootInfo": boot_info, - } - }) -} - -#[cfg(target_os = "macos")] -fn collect_macos_extended() -> serde_json::Value { - use std::process::Command; - // system_profiler em JSON (pode ser pesado; limitar a alguns tipos) - let profiler = Command::new("sh") - .arg("-lc") - .arg("system_profiler -json SPHardwareDataType SPSoftwareDataType SPNetworkDataType SPStorageDataType SPDisplaysDataType 2>/dev/null || true") - .output() - .ok() - .and_then(|out| serde_json::from_slice::(&out.stdout).ok()) - .unwrap_or_else(|| json!({})); - let pkgs = Command::new("sh") - .arg("-lc") - .arg("pkgutil --pkgs 2>/dev/null || true") - .output() - .ok() - .map(|out| { - String::from_utf8_lossy(&out.stdout) - .lines() - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect::>() - }) - .unwrap_or_default(); - let services_text = Command::new("sh") - .arg("-lc") - .arg("launchctl list 2>/dev/null || true") - .output() - .ok() - .map(|out| String::from_utf8_lossy(&out.stdout).to_string()) - .unwrap_or_default(); - - json!({ - "macos": { - "systemProfiler": profiler, - "packages": pkgs, - "launchctl": services_text, - } - }) -} - -fn collect_system() -> System { - let mut system = System::new_all(); - system.refresh_all(); - system -} - -fn collect_metrics(system: &System) -> MachineMetrics { - let collected_at = Utc::now(); - let total_memory = system.total_memory(); - let used_memory = system.used_memory(); - // sysinfo 0.31: valores já em bytes - let memory_total_bytes = total_memory; - let memory_used_bytes = used_memory; - let memory_used_percent = if total_memory > 0 { - (used_memory as f32 / total_memory as f32) * 100.0 - } else { - 0.0 - }; - - let load = System::load_average(); - let cpu_usage_percent = system.global_cpu_usage(); - let cpu_logical_cores = system.cpus().len(); - let cpu_physical_cores = system.physical_core_count(); - - MachineMetrics { - collected_at, - cpu_logical_cores, - cpu_physical_cores, - cpu_usage_percent, - load_average_one: Some(load.one), - load_average_five: Some(load.five), - load_average_fifteen: Some(load.fifteen), - memory_total_bytes, - memory_used_bytes, - memory_used_percent, - uptime_seconds: System::uptime(), - } -} - -pub fn collect_profile() -> Result { - let hostname = hostname::get() - .map_err(|_| AgentError::Hostname)? - .to_string_lossy() - .trim() - .to_string(); - - let system = collect_system(); - - let os_name = System::name() - .or_else(System::long_os_version) - .unwrap_or_else(|| "desconhecido".to_string()); - let os_version = System::os_version(); - let architecture = std::env::consts::ARCH.to_string(); - - let mac_addresses = collect_mac_addresses(); - let serials: Vec = collect_serials(); - - if mac_addresses.is_empty() && serials.is_empty() { - return Err(AgentError::MissingIdentifiers); - } - - let metrics = collect_metrics(&system); - let cpu_brand = system - .cpus() - .first() - .map(|cpu| cpu.brand().to_string()) - .filter(|brand| !brand.trim().is_empty()); - - let inventory = MachineInventory { - cpu_brand, - host_identifier: serials.first().cloned(), - }; - - Ok(MachineProfile { - hostname, - os: MachineOs { - name: os_name, - version: os_version, - architecture: Some(architecture), - }, - mac_addresses, - serial_numbers: serials, - inventory, - metrics, - }) -} - -static HTTP_CLIENT: Lazy = Lazy::new(|| { - reqwest::Client::builder() - .user_agent("sistema-de-chamados-agent/1.0") - .timeout(Duration::from_secs(20)) - .use_rustls_tls() - .build() - .expect("failed to build http client") -}); - -async fn post_heartbeat( - base_url: &str, - token: &str, - status: Option, -) -> Result<(), AgentError> { - let system = collect_system(); - let metrics = collect_metrics(&system); - let hostname = hostname::get() - .map_err(|_| AgentError::Hostname)? - .to_string_lossy() - .into_owned(); - let os = MachineOs { - name: System::name() - .or_else(System::long_os_version) - .unwrap_or_else(|| "desconhecido".to_string()), - version: System::os_version(), - architecture: Some(std::env::consts::ARCH.to_string()), - }; - - let payload = HeartbeatPayload { - machine_token: token.to_string(), - status, - hostname: Some(hostname), - os: Some(os), - metrics: Some(metrics), - metadata: Some(build_inventory_metadata(&system)), - }; - - let url = format!("{}/api/machines/heartbeat", base_url); - HTTP_CLIENT.post(url).json(&payload).send().await?; - Ok(()) -} - -#[derive(Debug, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -struct UsbPolicyResponse { - pending: bool, - policy: Option, - #[allow(dead_code)] - applied_at: Option, -} - -#[derive(Debug, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct UsbPolicyStatusReport { - machine_token: String, - status: String, - error: Option, - current_policy: Option, -} - -async fn check_and_apply_usb_policy(base_url: &str, token: &str) { - crate::log_info!("Verificando politica USB pendente..."); - - let url = format!("{}/api/machines/usb-policy?machineToken={}", base_url, token); - - let response = match HTTP_CLIENT.get(&url).send().await { - Ok(resp) => { - crate::log_info!("Resposta da verificacao de politica USB: status={}", resp.status()); - resp - } - Err(e) => { - crate::log_error!("Falha ao verificar politica USB: {e}"); - return; - } - }; - - let policy_response: UsbPolicyResponse = match response.json().await { - Ok(data) => data, - Err(e) => { - crate::log_error!("Falha ao parsear resposta de politica USB: {e}"); - return; - } - }; - - if !policy_response.pending { - crate::log_info!("Nenhuma politica USB pendente"); - return; - } - - let policy_str = match policy_response.policy { - Some(p) => p, - None => { - crate::log_warn!("Politica USB pendente mas sem valor de policy"); - return; - } - }; - - crate::log_info!("Politica USB pendente encontrada: {}", policy_str); - - #[cfg(target_os = "windows")] - { - use crate::usb_control::{get_current_policy, UsbPolicy}; - use crate::service_client; - - let policy = match UsbPolicy::from_str(&policy_str) { - Some(p) => p, - None => { - crate::log_error!("Politica USB invalida: {}", policy_str); - report_usb_policy_status(base_url, token, "FAILED", Some(format!("Politica invalida: {}", policy_str)), None).await; - return; - } - }; - - // Verifica se a politica ja esta aplicada localmente - match get_current_policy() { - Ok(current) if current == policy => { - crate::log_info!("Politica USB ja esta aplicada localmente: {}", policy_str); - let reported = report_usb_policy_status(base_url, token, "APPLIED", None, Some(policy_str.clone())).await; - if !reported { - crate::log_error!("Falha ao reportar politica ja aplicada"); - } - return; - } - Ok(current) => { - crate::log_info!("Politica atual: {:?}, esperada: {:?}", current, policy); - } - Err(e) => { - crate::log_warn!("Nao foi possivel ler politica atual: {e}"); - } - } - - crate::log_info!("Aplicando politica USB: {}", policy_str); - - // Reporta APPLYING para progress bar real no frontend - let _ = report_usb_policy_status(base_url, token, "APPLYING", None, None).await; - - // Tenta primeiro via RavenService (privilegiado) - crate::log_info!("Tentando aplicar politica via RavenService..."); - match service_client::apply_usb_policy(&policy_str) { - Ok(result) => { - if result.success { - crate::log_info!("Politica USB aplicada com sucesso via RavenService: {:?}", result); - let reported = report_usb_policy_status(base_url, token, "APPLIED", None, Some(policy_str.clone())).await; - if !reported { - crate::log_error!("CRITICO: Politica aplicada mas falha ao reportar ao servidor!"); - let base_url = base_url.to_string(); - let token = token.to_string(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(60)).await; - crate::log_info!("Retry agendado: reportando politica USB..."); - let _ = report_usb_policy_status(&base_url, &token, "APPLIED", None, Some(policy_str)).await; - }); - } - return; - } else { - let err_msg = result.error.unwrap_or_else(|| "Erro desconhecido".to_string()); - crate::log_error!("RavenService retornou erro: {}", err_msg); - report_usb_policy_status(base_url, token, "FAILED", Some(err_msg), None).await; - } - } - Err(service_client::ServiceClientError::ServiceUnavailable(msg)) => { - crate::log_warn!("RavenService nao disponivel: {}", msg); - // Tenta fallback direto (vai falhar se nao tiver privilegio) - crate::log_info!("Tentando aplicar politica diretamente..."); - match crate::usb_control::apply_usb_policy(policy) { - Ok(result) => { - crate::log_info!("Politica USB aplicada com sucesso (direto): {:?}", result); - let reported = report_usb_policy_status(base_url, token, "APPLIED", None, Some(policy_str.clone())).await; - if !reported { - crate::log_error!("CRITICO: Politica aplicada mas falha ao reportar ao servidor!"); - let base_url = base_url.to_string(); - let token = token.to_string(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(60)).await; - crate::log_info!("Retry agendado: reportando politica USB..."); - let _ = report_usb_policy_status(&base_url, &token, "APPLIED", None, Some(policy_str)).await; - }); - } - } - Err(e) => { - let err_msg = format!("RavenService indisponivel e aplicacao direta falhou: {}. Instale ou inicie o RavenService.", e); - crate::log_error!("{}", err_msg); - report_usb_policy_status(base_url, token, "FAILED", Some(err_msg), None).await; - } - } - } - Err(e) => { - crate::log_error!("Falha ao comunicar com RavenService: {e}"); - report_usb_policy_status(base_url, token, "FAILED", Some(e.to_string()), None).await; - } - } - } - - #[cfg(not(target_os = "windows"))] - { - crate::log_warn!("Controle de USB nao suportado neste sistema operacional"); - report_usb_policy_status(base_url, token, "FAILED", Some("Sistema operacional nao suportado".to_string()), None).await; - } -} - -async fn report_usb_policy_status( - base_url: &str, - token: &str, - status: &str, - error: Option, - current_policy: Option, -) -> bool { - let url = format!("{}/api/machines/usb-policy", base_url); - - let report = UsbPolicyStatusReport { - machine_token: token.to_string(), - status: status.to_string(), - error, - current_policy, - }; - - crate::log_info!("Reportando status de politica USB: status={}", status); - - // Retry simples: 1 tentativa imediata + 1 retry após 2s - let delays = [2]; - let mut last_error = None; - - for (attempt, delay_secs) in delays.iter().enumerate() { - match HTTP_CLIENT.post(&url).json(&report).send().await { - Ok(response) => { - let status_code = response.status(); - if status_code.is_success() { - crate::log_info!( - "Report de politica USB enviado com sucesso na tentativa {}", - attempt + 1 - ); - return true; - } else { - let body = response.text().await.unwrap_or_default(); - last_error = Some(format!("HTTP {} - {}", status_code, body)); - crate::log_warn!( - "Report de politica USB falhou (tentativa {}): HTTP {}", - attempt + 1, - status_code - ); - } - } - Err(e) => { - last_error = Some(e.to_string()); - crate::log_warn!( - "Report de politica USB falhou (tentativa {}): {}", - attempt + 1, - e - ); - } - } - - if attempt < delays.len() - 1 { - crate::log_info!("Retentando report de politica USB em {}s...", delay_secs); - tokio::time::sleep(Duration::from_secs(*delay_secs)).await; - } - } - - if let Some(err) = last_error { - crate::log_error!( - "Falha ao reportar status de politica USB apos {} tentativas: {err}", - delays.len() - ); - } - - false -} - -struct HeartbeatHandle { - token: String, - base_url: String, - stop_signal: Arc, - join_handle: JoinHandle<()>, -} - -impl HeartbeatHandle { - fn stop(self) { - self.stop_signal.notify_waiters(); - self.join_handle.abort(); - } -} - -#[derive(Default, Clone)] -pub struct AgentRuntime { - inner: Arc>>, -} - -fn sanitize_base_url(input: &str) -> Result { - let trimmed = input.trim().trim_end_matches('/'); - if trimmed.is_empty() { - return Err(AgentError::InvalidApiUrl); - } - Ok(trimmed.to_string()) -} - -impl AgentRuntime { - pub fn new() -> Self { - Self { - inner: Arc::new(Mutex::new(None)), - } - } - - pub fn start_heartbeat( - &self, - base_url: String, - token: String, - status: Option, - interval_seconds: Option, - ) -> Result<(), AgentError> { - let sanitized_base = sanitize_base_url(&base_url)?; - let interval = interval_seconds.unwrap_or(300).max(60); - - { - let mut guard = self.inner.lock(); - if let Some(handle) = guard.take() { - if handle.token == token && handle.base_url == sanitized_base { - // Reuse existing heartbeat; keep running. - *guard = Some(handle); - return Ok(()); - } - handle.stop(); - } - } - - let stop_signal = Arc::new(Notify::new()); - let stop_signal_clone = stop_signal.clone(); - let token_clone = token.clone(); - let base_clone = sanitized_base.clone(); - let status_clone = status.clone(); - - let join_handle = async_runtime::spawn(async move { - crate::log_info!("Loop de agente iniciado"); - - if let Err(error) = - post_heartbeat(&base_clone, &token_clone, status_clone.clone()).await - { - crate::log_error!("Falha inicial ao enviar heartbeat: {error}"); - } else { - crate::log_info!("Heartbeat inicial enviado com sucesso"); - } - - // Verifica politica USB apos heartbeat inicial - check_and_apply_usb_policy(&base_clone, &token_clone).await; - - let mut heartbeat_ticker = tokio::time::interval(Duration::from_secs(interval)); - heartbeat_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - let mut usb_ticker = tokio::time::interval(Duration::from_secs(15)); - usb_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - - loop { - // Wait interval - tokio::select! { - _ = stop_signal_clone.notified() => { - crate::log_info!("Loop de agente encerrado por sinal de parada"); - break; - } - _ = heartbeat_ticker.tick() => {} - _ = usb_ticker.tick() => { - check_and_apply_usb_policy(&base_clone, &token_clone).await; - continue; - } - } - - if let Err(error) = - post_heartbeat(&base_clone, &token_clone, status_clone.clone()).await - { - crate::log_error!("Falha ao enviar heartbeat: {error}"); - } - - // Verifica politica USB apos cada heartbeat - check_and_apply_usb_policy(&base_clone, &token_clone).await; - } - }); - - let handle = HeartbeatHandle { - token, - base_url: sanitized_base, - stop_signal, - join_handle, - }; - - let mut guard = self.inner.lock(); - *guard = Some(handle); - - Ok(()) - } - - pub fn stop(&self) { - let mut guard = self.inner.lock(); - if let Some(handle) = guard.take() { - handle.stop(); - } - } -} - -#[cfg(all(test, target_os = "windows"))] -mod windows_tests { - use super::collect_windows_extended; - use serde_json::Value; - - fn expect_object<'a>(value: &'a Value, context: &str) -> &'a serde_json::Map { - value - .as_object() - .unwrap_or_else(|| panic!("{context} não é um objeto JSON: {value:?}")) - } - - #[test] - fn collects_activation_and_defender_status() { - let extended = collect_windows_extended(); - let windows = extended.get("windows").unwrap_or_else(|| { - panic!("payload windows ausente: {extended:?}"); - }); - let windows_obj = expect_object(windows, "windows"); - - let os_info = windows_obj - .get("osInfo") - .unwrap_or_else(|| panic!("windows.osInfo ausente: {windows_obj:?}")); - let os_info_obj = expect_object(os_info, "windows.osInfo"); - - let is_activated = os_info_obj.get("IsActivated").unwrap_or_else(|| { - panic!("campo IsActivated ausente em windows.osInfo: {os_info_obj:?}") - }); - assert!( - is_activated.as_bool().is_some(), - "esperava booleano em windows.osInfo.IsActivated, valor recebido: {is_activated:?}" - ); - - let license_status = os_info_obj.get("LicenseStatus").unwrap_or_else(|| { - panic!("campo LicenseStatus ausente em windows.osInfo: {os_info_obj:?}") - }); - assert!( - license_status.as_i64().is_some(), - "esperava número em windows.osInfo.LicenseStatus, valor recebido: {license_status:?}" - ); - - let defender = windows_obj.get("defender").unwrap_or_else(|| { - panic!("windows.defender ausente: {windows_obj:?}"); - }); - let defender_obj = expect_object(defender, "windows.defender"); - - let realtime = defender_obj - .get("RealTimeProtectionEnabled") - .unwrap_or_else(|| { - panic!( - "campo RealTimeProtectionEnabled ausente em windows.defender: {defender_obj:?}" - ) - }); - assert!( - realtime.as_bool().is_some(), - "esperava booleano em windows.defender.RealTimeProtectionEnabled, valor recebido: {realtime:?}" - ); - } -} diff --git a/apps/desktop/src-tauri/src/chat.rs b/apps/desktop/src-tauri/src/chat.rs deleted file mode 100644 index 7c924de..0000000 --- a/apps/desktop/src-tauri/src/chat.rs +++ /dev/null @@ -1,1378 +0,0 @@ -//! Modulo de Chat em Tempo Real -//! -//! Este modulo implementa o sistema de chat entre agentes (dashboard web) -//! e clientes (Raven desktop). Usa Server-Sent Events (SSE) como metodo -//! primario para atualizacoes em tempo real, com fallback para HTTP polling. - -use convex::{ConvexClient, FunctionResult, Value}; -use futures_util::StreamExt; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::fs; -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use tauri::async_runtime::JoinHandle; -use tauri::{Emitter, Manager, WebviewWindowBuilder, WebviewUrl}; -use tauri_plugin_notification::NotificationExt; - -// ============================================================================ -// TYPES -// ============================================================================ - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatSession { - pub session_id: String, - pub ticket_id: String, - pub ticket_ref: u64, - pub ticket_subject: String, - pub agent_name: String, - pub agent_email: Option, - pub agent_avatar_url: Option, - pub unread_count: u32, - pub last_activity_at: i64, - pub started_at: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatMessage { - pub id: String, - pub body: String, - pub author_name: String, - pub author_avatar_url: Option, - pub is_from_machine: bool, - pub created_at: i64, - pub attachments: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatAttachment { - pub storage_id: String, - pub name: String, - pub size: Option, - #[serde(rename = "type")] - pub mime_type: Option, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatPollResponse { - pub has_active_sessions: bool, - pub sessions: Vec, - pub total_unread: u32, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatSessionSummary { - pub ticket_id: String, - pub ticket_ref: u64, - pub unread_count: u32, - pub last_activity_at: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatMessagesResponse { - pub messages: Vec, - pub has_session: bool, - #[serde(default)] - pub unread_count: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SendMessageResponse { - pub message_id: String, - pub created_at: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionStartedEvent { - pub session: ChatSession, -} - -// ============================================================================ -// PERSISTENCIA DE ESTADO -// ============================================================================ - -/// Estado persistido do chat para sobreviver a restarts -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ChatPersistedState { - last_unread_count: u32, - sessions: Vec, - saved_at: u64, // Unix timestamp em ms -} - -const STATE_FILE_NAME: &str = "chat-state.json"; -const STATE_MAX_AGE_MS: u64 = 3600_000; // 1 hora - ignorar estados mais antigos - -fn get_state_file_path() -> Option { - dirs::data_local_dir().map(|p| p.join("Raven").join(STATE_FILE_NAME)) -} - -fn save_chat_state(last_unread: u32, sessions: &[ChatSession]) { - let Some(path) = get_state_file_path() else { - return; - }; - - // Criar diretorio se nao existir - if let Some(parent) = path.parent() { - let _ = fs::create_dir_all(parent); - } - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_millis() as u64) - .unwrap_or(0); - - let state = ChatPersistedState { - last_unread_count: last_unread, - sessions: sessions.to_vec(), - saved_at: now, - }; - - if let Ok(json) = serde_json::to_string_pretty(&state) { - let _ = fs::write(&path, json); - crate::log_info!("[CHAT] Estado persistido: unread={}, sessions={}", last_unread, sessions.len()); - } -} - -fn load_chat_state() -> Option { - let path = get_state_file_path()?; - - let json = fs::read_to_string(&path).ok()?; - let state: ChatPersistedState = serde_json::from_str(&json).ok()?; - - // Verificar se estado nao esta muito antigo - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_millis() as u64) - .unwrap_or(0); - - if now.saturating_sub(state.saved_at) > STATE_MAX_AGE_MS { - crate::log_info!("[CHAT] Estado persistido ignorado (muito antigo)"); - return None; - } - - crate::log_info!( - "[CHAT] Estado restaurado: unread={}, sessions={}", - state.last_unread_count, state.sessions.len() - ); - Some(state) -} - -// ============================================================================ -// HTTP CLIENT -// ============================================================================ - -static CHAT_CLIENT: Lazy = Lazy::new(|| { - Client::builder() - .user_agent("raven-chat/1.0") - .timeout(Duration::from_secs(15)) - .use_rustls_tls() - .build() - .expect("failed to build chat http client") -}); - -// ============================================================================ -// API FUNCTIONS -// ============================================================================ - -#[allow(dead_code)] -pub async fn poll_chat_updates( - base_url: &str, - token: &str, - last_checked_at: Option, -) -> Result { - let url = format!("{}/api/machines/chat/poll", base_url); - - let mut payload = serde_json::json!({ - "machineToken": token, - }); - - if let Some(ts) = last_checked_at { - payload["lastCheckedAt"] = serde_json::json!(ts); - } - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de poll: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Poll falhou: status={}, body={}", status, body)); - } - - response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de poll: {e}")) -} - -pub async fn fetch_sessions(base_url: &str, token: &str) -> Result, String> { - let url = format!("{}/api/machines/chat/sessions", base_url); - - let payload = serde_json::json!({ - "machineToken": token, - }); - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de sessions: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Sessions falhou: status={}, body={}", status, body)); - } - - #[derive(Deserialize)] - struct SessionsResponse { - sessions: Vec, - } - - let data: SessionsResponse = response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de sessions: {e}"))?; - - Ok(data.sessions) -} - -pub async fn fetch_messages( - base_url: &str, - token: &str, - ticket_id: &str, - since: Option, -) -> Result { - let url = format!("{}/api/machines/chat/messages", base_url); - - let mut payload = serde_json::json!({ - "machineToken": token, - "ticketId": ticket_id, - "action": "list", - "limit": 200, - }); - - if let Some(ts) = since { - payload["since"] = serde_json::json!(ts); - } - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de messages: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Messages falhou: status={}, body={}", status, body)); - } - - response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de messages: {e}")) -} - -pub async fn send_message( - base_url: &str, - token: &str, - ticket_id: &str, - body: &str, - attachments: Option>, -) -> Result { - let url = format!("{}/api/machines/chat/messages", base_url); - - let mut payload = serde_json::json!({ - "machineToken": token, - "ticketId": ticket_id, - "action": "send", - "body": body, - }); - - if let Some(atts) = attachments { - payload["attachments"] = serde_json::to_value(atts).unwrap_or_default(); - } - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de send: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Send falhou: status={}, body={}", status, body)); - } - - response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de send: {e}")) -} - -pub async fn mark_messages_read( - base_url: &str, - token: &str, - ticket_id: &str, - message_ids: &[String], -) -> Result<(), String> { - let url = format!("{}/api/machines/chat/read", base_url); - - let payload = serde_json::json!({ - "machineToken": token, - "ticketId": ticket_id, - "messageIds": message_ids, - }); - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de mark read: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Mark read falhou: status={}, body={}", status, body)); - } - - Ok(()) -} - -// ============================================================================ -// UPLOAD DE ARQUIVOS -// ============================================================================ - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AttachmentPayload { - pub storage_id: String, - pub name: String, - pub size: Option, - #[serde(rename = "type")] - pub mime_type: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UploadUrlResponse { - pub upload_url: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UploadResult { - pub storage_id: String, -} - -// Extensoes permitidas -const ALLOWED_EXTENSIONS: &[&str] = &[ - ".jpg", ".jpeg", ".png", ".gif", ".webp", - ".pdf", ".txt", ".doc", ".docx", ".xls", ".xlsx", -]; - -// Tamanho maximo: 10MB -const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024; - -pub fn is_allowed_file(file_name: &str, file_size: u64) -> Result<(), String> { - let ext = file_name - .to_lowercase() - .rsplit('.') - .next() - .map(|e| format!(".{}", e)) - .unwrap_or_default(); - - if !ALLOWED_EXTENSIONS.contains(&ext.as_str()) { - return Err(format!( - "Tipo de arquivo não permitido. Permitidos: {}", - ALLOWED_EXTENSIONS.join(", ") - )); - } - - if file_size > MAX_FILE_SIZE { - return Err(format!( - "Arquivo muito grande. Máximo: {}MB", - MAX_FILE_SIZE / 1024 / 1024 - )); - } - - Ok(()) -} - -pub fn get_mime_type(file_name: &str) -> String { - let lower = file_name.to_lowercase(); - let ext = lower.rsplit('.').next().unwrap_or(""); - - match ext { - "jpg" | "jpeg" => "image/jpeg", - "png" => "image/png", - "gif" => "image/gif", - "webp" => "image/webp", - "pdf" => "application/pdf", - "txt" => "text/plain", - "doc" => "application/msword", - "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "xls" => "application/vnd.ms-excel", - "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - _ => "application/octet-stream", - } - .to_string() -} - -pub async fn generate_upload_url( - base_url: &str, - token: &str, - file_name: &str, - file_type: &str, - file_size: u64, -) -> Result { - let url = format!("{}/api/machines/chat/upload", base_url); - - let payload = serde_json::json!({ - "machineToken": token, - "fileName": file_name, - "fileType": file_type, - "fileSize": file_size, - }); - - let response = CHAT_CLIENT - .post(&url) - .json(&payload) - .send() - .await - .map_err(|e| format!("Falha na requisicao de upload URL: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Upload URL falhou: status={}, body={}", status, body)); - } - - let data: UploadUrlResponse = response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de upload URL: {e}"))?; - - Ok(data.upload_url) -} - -pub async fn upload_file( - upload_url: &str, - file_data: Vec, - content_type: &str, -) -> Result { - let response = CHAT_CLIENT - .post(upload_url) - .header("Content-Type", content_type) - .body(file_data) - .send() - .await - .map_err(|e| format!("Falha no upload: {e}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("Upload falhou: status={}, body={}", status, body)); - } - - let data: UploadResult = response - .json() - .await - .map_err(|e| format!("Falha ao parsear resposta de upload: {e}"))?; - - Ok(data.storage_id) -} - -// ============================================================================ -// CHAT RUNTIME -// ============================================================================ - -struct ChatRealtimeHandle { - stop_flag: Arc, - join_handle: JoinHandle<()>, -} - -impl ChatRealtimeHandle { - fn stop(self) { - self.stop_flag.store(true, Ordering::Relaxed); - self.join_handle.abort(); - } -} - -#[derive(Default, Clone)] -pub struct ChatRuntime { - inner: Arc>>, - last_sessions: Arc>>, - last_unread_count: Arc>, - is_connected: Arc, -} - -impl ChatRuntime { - pub fn new() -> Self { - // Tentar restaurar estado persistido - let (sessions, unread) = match load_chat_state() { - Some(state) => (state.sessions, state.last_unread_count), - None => (Vec::new(), 0), - }; - - Self { - inner: Arc::new(Mutex::new(None)), - last_sessions: Arc::new(Mutex::new(sessions)), - last_unread_count: Arc::new(Mutex::new(unread)), - is_connected: Arc::new(AtomicBool::new(false)), - } - } - - /// Retorna true se conexao WS Convex esta ativa - pub fn is_using_sse(&self) -> bool { - self.is_connected.load(Ordering::Relaxed) - } - - /// Inicia o sistema de atualizacoes de chat via WebSocket do Convex - pub fn start_polling( - &self, - base_url: String, - convex_url: String, - token: String, - app: tauri::AppHandle, - ) -> Result<(), String> { - let sanitized_base = base_url.trim().trim_end_matches('/').to_string(); - if sanitized_base.is_empty() { - return Err("URL base invalida".to_string()); - } - let sanitized_convex = convex_url.trim().trim_end_matches('/').to_string(); - if sanitized_convex.is_empty() { - return Err("URL do Convex inválida".to_string()); - } - - // Para polling/SSE existente - { - let mut guard = self.inner.lock(); - if let Some(handle) = guard.take() { - handle.stop(); - } - } - - let stop_flag = Arc::new(AtomicBool::new(false)); - let stop_clone = stop_flag.clone(); - let base_clone = sanitized_base.clone(); - let convex_clone = sanitized_convex.clone(); - let token_clone = token.clone(); - let last_sessions = self.last_sessions.clone(); - let last_unread_count = self.last_unread_count.clone(); - let is_connected = self.is_connected.clone(); - - let join_handle = tauri::async_runtime::spawn(async move { - crate::log_info!("[CHAT DEBUG] Iniciando sistema de chat"); - crate::log_info!("[CHAT DEBUG] Convex URL: {}", convex_clone); - crate::log_info!("[CHAT DEBUG] API Base URL: {}", base_clone); - - let mut backoff_ms: u64 = 1_000; - let max_backoff_ms: u64 = 30_000; - let poll_interval = Duration::from_secs(5); - let mut last_poll = Instant::now() - poll_interval; - - loop { - if stop_clone.load(Ordering::Relaxed) { - break; - } - - crate::log_info!("[CHAT DEBUG] Tentando conectar ao Convex..."); - let client_result = ConvexClient::new(&convex_clone).await; - let mut client = match client_result { - Ok(c) => { - crate::log_info!("[CHAT DEBUG] Cliente Convex criado com sucesso"); - c - } - Err(err) => { - is_connected.store(false, Ordering::Relaxed); - crate::log_warn!("[CHAT DEBUG] FALHA ao criar cliente Convex: {err:?}"); - - if last_poll.elapsed() >= poll_interval { - poll_and_process_chat_update( - &base_clone, - &token_clone, - &app, - &last_sessions, - &last_unread_count, - ) - .await; - last_poll = Instant::now(); - } - - tokio::time::sleep(Duration::from_millis(backoff_ms)).await; - backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms); - continue; - } - }; - - let mut args = BTreeMap::new(); - args.insert("machineToken".to_string(), token_clone.clone().into()); - - crate::log_info!("[CHAT DEBUG] Assinando liveChat:checkMachineUpdates..."); - let subscribe_result = client.subscribe("liveChat:checkMachineUpdates", args).await; - let mut subscription = match subscribe_result { - Ok(sub) => { - is_connected.store(true, Ordering::Relaxed); - backoff_ms = 1_000; - crate::log_info!("[CHAT DEBUG] CONECTADO ao Convex WebSocket com sucesso!"); - sub - } - Err(err) => { - is_connected.store(false, Ordering::Relaxed); - crate::log_warn!("[CHAT DEBUG] FALHA ao assinar checkMachineUpdates: {err:?}"); - - if last_poll.elapsed() >= poll_interval { - poll_and_process_chat_update( - &base_clone, - &token_clone, - &app, - &last_sessions, - &last_unread_count, - ) - .await; - last_poll = Instant::now(); - } - - tokio::time::sleep(Duration::from_millis(backoff_ms)).await; - backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms); - continue; - } - }; - - crate::log_info!("[CHAT DEBUG] Entrando no loop de escuta WebSocket..."); - let mut update_count: u64 = 0; - while let Some(next) = subscription.next().await { - update_count += 1; - if stop_clone.load(Ordering::Relaxed) { - crate::log_info!("[CHAT DEBUG] Stop flag detectado, saindo do loop"); - break; - } - match next { - FunctionResult::Value(Value::Object(obj)) => { - let has_active = obj - .get("hasActiveSessions") - .and_then(|v| match v { - Value::Boolean(b) => Some(*b), - _ => None, - }) - .unwrap_or(false); - let total_unread = obj - .get("totalUnread") - .and_then(|v| match v { - Value::Int64(i) => Some(*i as u32), - Value::Float64(f) => Some(*f as u32), - _ => None, - }) - .unwrap_or(0); - - crate::log_info!( - "[CHAT DEBUG] UPDATE #{} recebido via WebSocket: hasActive={}, totalUnread={}", - update_count, has_active, total_unread - ); - - process_chat_update( - &base_clone, - &token_clone, - &app, - &last_sessions, - &last_unread_count, - has_active, - total_unread, - ) - .await; - } - FunctionResult::ConvexError(err) => { - crate::log_warn!("[CHAT DEBUG] Convex error em checkMachineUpdates: {err:?}"); - } - FunctionResult::ErrorMessage(msg) => { - crate::log_warn!("[CHAT DEBUG] Erro em checkMachineUpdates: {msg}"); - } - FunctionResult::Value(other) => { - crate::log_warn!("[CHAT DEBUG] Payload inesperado em checkMachineUpdates: {other:?}"); - } - } - } - - is_connected.store(false, Ordering::Relaxed); - - if stop_clone.load(Ordering::Relaxed) { - crate::log_info!("[CHAT DEBUG] Stop flag detectado apos loop"); - break; - } - - crate::log_warn!("[CHAT DEBUG] WebSocket DESCONECTADO! Aplicando fallback e tentando reconectar..."); - if last_poll.elapsed() >= poll_interval { - poll_and_process_chat_update( - &base_clone, - &token_clone, - &app, - &last_sessions, - &last_unread_count, - ) - .await; - last_poll = Instant::now(); - } - - tokio::time::sleep(Duration::from_millis(backoff_ms)).await; - backoff_ms = backoff_ms.saturating_mul(2).min(max_backoff_ms); - } - - is_connected.store(false, Ordering::Relaxed); - crate::log_info!("Chat encerrado (realtime finalizado)"); - }); - - let mut guard = self.inner.lock(); - *guard = Some(ChatRealtimeHandle { - stop_flag, - join_handle, - }); - - Ok(()) - } - - pub fn stop(&self) { - let mut guard = self.inner.lock(); - if let Some(handle) = guard.take() { - handle.stop(); - } - self.is_connected.store(false, Ordering::Relaxed); - } - - pub fn get_sessions(&self) -> Vec { - self.last_sessions.lock().clone() - } -} - -// ============================================================================ -// SHARED UPDATE PROCESSING -// ============================================================================ - -async fn poll_and_process_chat_update( - base_url: &str, - token: &str, - app: &tauri::AppHandle, - last_sessions: &Arc>>, - last_unread_count: &Arc>, -) { - crate::log_info!("[CHAT DEBUG] Executando fallback HTTP polling..."); - match poll_chat_updates(base_url, token, None).await { - Ok(result) => { - crate::log_info!( - "[CHAT DEBUG] Polling OK: hasActive={}, totalUnread={}", - result.has_active_sessions, result.total_unread - ); - process_chat_update( - base_url, - token, - app, - last_sessions, - last_unread_count, - result.has_active_sessions, - result.total_unread, - ) - .await; - } - Err(err) => { - crate::log_warn!("[CHAT DEBUG] Fallback poll FALHOU: {err}"); - } - } -} - -async fn process_chat_update( - base_url: &str, - token: &str, - app: &tauri::AppHandle, - last_sessions: &Arc>>, - last_unread_count: &Arc>, - has_active_sessions: bool, - total_unread: u32, -) { - crate::log_info!( - "[CHAT DEBUG] process_chat_update: hasActive={}, totalUnread={}", - has_active_sessions, total_unread - ); - - // Buscar sessoes completas para ter dados corretos - let mut current_sessions = if has_active_sessions { - let sessions = fetch_sessions(base_url, token).await.unwrap_or_default(); - crate::log_info!("[CHAT DEBUG] Buscou {} sessoes ativas", sessions.len()); - sessions - } else { - crate::log_info!("[CHAT DEBUG] Sem sessoes ativas"); - Vec::new() - }; - - // Ordenar por ultima atividade (mais recente primeiro) para consistencia em UI/tray. - if current_sessions.len() > 1 { - current_sessions.sort_by(|a, b| { - b.last_activity_at - .cmp(&a.last_activity_at) - .then_with(|| b.started_at.cmp(&a.started_at)) - }); - } - - // Verificar sessoes anteriores - let prev_sessions: Vec = last_sessions.lock().clone(); - let prev_session_ids: Vec = prev_sessions.iter().map(|s| s.session_id.clone()).collect(); - let current_session_ids: Vec = current_sessions.iter().map(|s| s.session_id.clone()).collect(); - - // Detectar novas sessoes - for session in ¤t_sessions { - if !prev_session_ids.contains(&session.session_id) { - crate::log_info!( - "Nova sessao de chat: ticket={}, session={}", - session.ticket_id, - session.session_id - ); - let _ = app.emit( - "raven://chat/session-started", - SessionStartedEvent { - session: session.clone(), - }, - ); - - // NÃO abre janela aqui - só quando o agente enviar a primeira mensagem - // O chat aparecerá minimizado com badge quando houver novas mensagens - - crate::log_info!( - "Sessão de chat iniciada pelo agente {}. Aguardando primeira mensagem.", - session.agent_name - ); - } - } - - // Detectar sessoes encerradas - for prev_session in &prev_sessions { - if !current_session_ids.contains(&prev_session.session_id) { - crate::log_info!( - "Sessao de chat encerrada: ticket={}, session={}", - prev_session.ticket_id, - prev_session.session_id - ); - let _ = app.emit( - "raven://chat/session-ended", - serde_json::json!({ - "sessionId": prev_session.session_id, - "ticketId": prev_session.ticket_id - }), - ); - } - } - - // ========================================================================= - // DETECCAO ROBUSTA DE NOVAS MENSAGENS - // Usa DUAS estrategias: timestamp E contador (belt and suspenders) - // ========================================================================= - - let prev_unread = *last_unread_count.lock(); - - // Estrategia 1: Detectar por lastActivityAt de cada sessao - // Se alguma sessao teve atividade mais recente E tem mensagens nao lidas -> nova mensagem - let mut detected_by_activity = false; - let mut activity_details = String::new(); - - for session in ¤t_sessions { - let prev_activity = prev_sessions - .iter() - .find(|s| s.session_id == session.session_id) - .map(|s| s.last_activity_at) - .unwrap_or(0); - - // Se lastActivityAt aumentou E ha mensagens nao lidas -> nova mensagem do agente - if session.last_activity_at > prev_activity && session.unread_count > 0 { - detected_by_activity = true; - activity_details = format!( - "sessao={} activity: {} -> {} unread={}", - session.ticket_id, prev_activity, session.last_activity_at, session.unread_count - ); - break; - } - } - - // Estrategia 2: Fallback por contador total (metodo original) - let detected_by_count = total_unread > prev_unread; - - // Nova mensagem se QUALQUER estrategia detectar - let new_messages = detected_by_activity || detected_by_count; - - // Log detalhado para diagnostico - crate::log_info!( - "[CHAT] Deteccao: by_activity={} by_count={} (prev={} curr={}) resultado={}", - detected_by_activity, detected_by_count, prev_unread, total_unread, new_messages - ); - if detected_by_activity { - crate::log_info!("[CHAT] Detectado por atividade: {}", activity_details); - } - - // Atualizar caches APOS deteccao (importante: manter ordem) - *last_sessions.lock() = current_sessions.clone(); - *last_unread_count.lock() = total_unread; - - // Persistir estado para sobreviver a restarts - save_chat_state(total_unread, ¤t_sessions); - - // Sempre emitir unread-update - let _ = app.emit( - "raven://chat/unread-update", - serde_json::json!({ - "totalUnread": total_unread, - "sessions": current_sessions - }), - ); - - // Notificar novas mensagens - mostrar chat minimizado com badge - if new_messages && total_unread > 0 { - let new_count = if total_unread > prev_unread { - total_unread - prev_unread - } else { - 1 // Se detectou por activity mas contador nao mudou, assumir 1 nova - }; - - crate::log_info!( - "[CHAT] NOVAS MENSAGENS! count={}, total={}, metodo={}", - new_count, total_unread, - if detected_by_activity { "activity" } else { "count" } - ); - - let _ = app.emit( - "raven://chat/new-message", - serde_json::json!({ - "totalUnread": total_unread, - "newCount": new_count, - "sessions": current_sessions - }), - ); - - // Escolher qual sessao/ticket deve ser mostrado quando ha multiplas sessoes. - // Preferencia: maior incremento de unread (delta) e, em empate, ultima atividade mais recente. - let mut best_session: Option<&ChatSession> = None; - let mut best_delta: u32 = 0; - - for session in ¤t_sessions { - let prev_unread_for_ticket = prev_sessions - .iter() - .find(|s| s.ticket_id == session.ticket_id) - .map(|s| s.unread_count) - .unwrap_or(0); - let delta = session.unread_count.saturating_sub(prev_unread_for_ticket); - - let is_better = if delta > best_delta { - true - } else if delta == best_delta { - match best_session { - Some(best) => session.last_activity_at > best.last_activity_at, - None => true, - } - } else { - false - }; - - if is_better { - best_delta = delta; - best_session = Some(session); - } - } - - // Se ha multiplas sessoes ativas, usar o hub; senao, abrir janela do chat individual. - // - // Importante (UX): em multiplas sessoes, NAO fecha a janela ativa quando chega mensagem em outra conversa. - // O hub + badge/notificacao sinalizam novas mensagens e o usuario decide quando alternar. - if current_sessions.len() > 1 { - let _ = open_hub_window(app); - } else { - // Uma sessao - nao precisa de hub - let _ = close_hub_window(app); - - // Fallback: se nao conseguimos detectar delta, pega a sessao com mais unread e mais recente. - let session_to_show = if best_delta > 0 { - best_session - } else { - current_sessions.iter().max_by(|a, b| { - a.unread_count - .cmp(&b.unread_count) - .then_with(|| a.last_activity_at.cmp(&b.last_activity_at)) - }) - }; - - // Mostrar janela de chat (sempre minimizada/nao intrusiva) - if let Some(session) = session_to_show { - let _ = open_chat_window_internal(app, &session.ticket_id, session.ticket_ref, true); - } - } - - // Notificacao nativa - let notification_title = "Nova mensagem de suporte"; - let notification_body = if new_count == 1 { - "Você recebeu 1 nova mensagem no chat".to_string() - } else { - format!("Você recebeu {} novas mensagens no chat", new_count) - }; - let _ = app - .notification() - .builder() - .title(notification_title) - .body(¬ification_body) - .show(); - } else { - // Log para debug quando NAO ha novas mensagens - if total_unread == 0 { - crate::log_info!("[CHAT DEBUG] Sem mensagens nao lidas (total=0)"); - } else if !new_messages { - crate::log_info!( - "[CHAT DEBUG] Sem novas mensagens (prev={} >= total={})", - prev_unread, total_unread - ); - } - } -} - -// ============================================================================ -// WINDOW MANAGEMENT -// ============================================================================ - -// Serializa operacoes de janela para evitar race/deadlock no Windows (winit/WebView2). -static WINDOW_OP_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); - -fn hide_other_chat_windows(app: &tauri::AppHandle, active_label: &str) { - for (label, window) in app.webview_windows() { - if !label.starts_with("chat-") { - continue; - } - if label == active_label { - continue; - } - let _ = window.hide(); - } - if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) { - let _ = hub.hide(); - } -} - -fn resolve_chat_window_position( - app: &tauri::AppHandle, - window: Option<&tauri::WebviewWindow>, - width: f64, - height: f64, -) -> (f64, f64) { - let margin = 20.0; - let taskbar_height = 50.0; - - let monitor = window - .and_then(|w| w.current_monitor().ok().flatten()) - .or_else(|| { - app.get_webview_window("main") - .and_then(|w| w.current_monitor().ok().flatten()) - }) - .or_else(|| app.available_monitors().ok().and_then(|monitors| monitors.into_iter().next())); - - let Some(monitor) = monitor else { - return (100.0, 100.0); - }; - - let size = monitor.size(); - let pos = monitor.position(); - let scale = monitor.scale_factor(); - - // Converter coordenadas do monitor para coordenadas logicas (multi-monitor pode ter origem negativa). - let monitor_x = pos.x as f64 / scale; - let monitor_y = pos.y as f64 / scale; - let monitor_width = size.width as f64 / scale; - let monitor_height = size.height as f64 / scale; - - let max_x = monitor_x + monitor_width - width - margin; - let max_y = monitor_y + monitor_height - height - margin - taskbar_height; - - let x = if max_x.is_finite() { max_x.max(monitor_x) } else { 100.0 }; - let y = if max_y.is_finite() { max_y.max(monitor_y) } else { 100.0 }; - - (x, y) -} - -fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - open_chat_window_with_state(app, ticket_id, ticket_ref, start_minimized) -} - -/// Abre janela de chat com estado inicial de minimizacao configuravel -fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> { - let label = format!("chat-{}", ticket_id); - crate::log_info!( - "[WINDOW] open_chat_window: label={} ticket_ref={} start_minimized={}", - label, - ticket_ref, - start_minimized - ); - - if !start_minimized { - hide_other_chat_windows(app, &label); - } - - // Verificar se ja existe - if let Some(window) = app.get_webview_window(&label) { - let _ = window.set_ignore_cursor_events(false); - crate::log_info!("[WINDOW] {}: window existe -> show()", label); - window.show().map_err(|e| e.to_string())?; - let _ = window.unminimize(); - if !start_minimized { - crate::log_info!("[WINDOW] {}: window existe -> set_focus()", label); - window.set_focus().map_err(|e| e.to_string())?; - } - // Expandir a janela se estiver minimizada (quando clicado na lista) - if !start_minimized { - crate::log_info!("[WINDOW] {}: window existe -> set_chat_minimized(false)", label); - let _ = set_chat_minimized_unlocked(app, ticket_id, false); - } - crate::log_info!("[WINDOW] {}: open_chat_window OK (reuso)", label); - return Ok(()); - } - - // Dimensoes baseadas no estado inicial - let (width, height) = if start_minimized { - (240.0, 52.0) // Tamanho minimizado (chip com badge) - } else { - (380.0, 520.0) // Tamanho expandido - }; - - // Posicionar no canto inferior direito (acima da barra de tarefas). - let (x, y) = resolve_chat_window_position(app, None, width, height); - - // Usar query param ao inves de path para compatibilidade com SPA - let url_path = format!("index.html?view=chat&ticketId={}&ticketRef={}", ticket_id, ticket_ref); - - crate::log_info!( - "[WINDOW] {}: build() inicio size={}x{} pos=({},{}) url={}", - label, - width, - height, - x, - y, - url_path - ); - - let window = WebviewWindowBuilder::new( - app, - &label, - WebviewUrl::App(url_path.into()), - ) - .title("Chat de Suporte") - .inner_size(width, height) // Abre ja no tamanho correto - .min_inner_size(240.0, 52.0) // Tamanho minimo para modo minimizado com badge - .position(x, y) - .decorations(false) // Sem decoracoes nativas - usa header customizado - .transparent(true) // Permite fundo transparente - .shadow(false) // Desabilitar sombra para transparencia funcionar corretamente - .resizable(false) // Desabilitar redimensionamento manual - // Mantem o chat acessivel mesmo ao trocar de janela/app (skip_taskbar=true). - .always_on_top(true) - .skip_taskbar(true) - .focused(!start_minimized) - .visible(true) - .build() - .map_err(|e| e.to_string())?; - - crate::log_info!("[WINDOW] {}: build() OK", label); - - // IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through) - let _ = window.set_ignore_cursor_events(false); - - crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) inicio", label, start_minimized); - // Reaplica layout/posicao logo apos criar a janela. - // Isso evita que a primeira abertura apareca no canto superior esquerdo em alguns ambientes. - let _ = set_chat_minimized_unlocked(app, ticket_id, start_minimized); - crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) fim", label, start_minimized); - - crate::log_info!("Janela de chat aberta (minimizada={}): {}", start_minimized, label); - Ok(()) -} - -pub fn open_chat_window(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> { - // Quando chamado explicitamente (ex: clique no hub), abre expandida - open_chat_window_internal(app, ticket_id, ticket_ref, false) -} - -pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - let label = format!("chat-{}", ticket_id); - if let Some(window) = app.get_webview_window(&label) { - window.close().map_err(|e| e.to_string())?; - } - Ok(()) -} - -pub fn minimize_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - let label = format!("chat-{}", ticket_id); - if let Some(window) = app.get_webview_window(&label) { - window.hide().map_err(|e| e.to_string())?; - } - Ok(()) -} - -/// Redimensiona a janela de chat para modo minimizado (chip) ou expandido -fn set_chat_minimized_unlocked(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> { - let label = format!("chat-{}", ticket_id); - let window = app.get_webview_window(&label).ok_or("Janela não encontrada")?; - - if minimized { - hide_other_chat_windows(app, &label); - } - - // Tamanhos - chip minimizado com margem extra para badge (absolute -top-1 -right-1) - let (width, height) = if minimized { - (240.0, 52.0) // Tamanho com folga para "Ticket #XXX" e badge - } else { - (380.0, 520.0) // Tamanho expandido - }; - - // Calcular posicao no canto inferior direito do monitor atual (com fallback seguro). - let (x, y) = resolve_chat_window_position(app, Some(&window), width, height); - - // Aplicar novo tamanho e posicao - crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size inicio", label, minimized); - window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?; - crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size OK", label, minimized); - crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position inicio", label, minimized); - window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?; - crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position OK", label, minimized); - - crate::log_info!("Chat {} -> minimized={}", ticket_id, minimized); - Ok(()) -} - -pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - set_chat_minimized_unlocked(app, ticket_id, minimized) -} - -// ============================================================================ -// HUB WINDOW MANAGEMENT (Lista de todas as sessoes) -// ============================================================================ - -const HUB_WINDOW_LABEL: &str = "chat-hub"; - -pub fn open_hub_window(app: &tauri::AppHandle) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - open_hub_window_with_state(app, true) // Por padrao abre minimizada -} - -fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) -> Result<(), String> { - // Verificar se ja existe - if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) { - let _ = window.set_ignore_cursor_events(false); - window.show().map_err(|e| e.to_string())?; - let _ = window.unminimize(); - if !start_minimized { - window.set_focus().map_err(|e| e.to_string())?; - } - return Ok(()); - } - - // Dimensoes baseadas no estado inicial - let (width, height) = if start_minimized { - (200.0, 52.0) // Tamanho minimizado (chip) - } else { - (400.0, 520.0) // Tamanho expandido (igual ao web) - }; - - // Posicionar no canto inferior direito - let (x, y) = resolve_chat_window_position(app, None, width, height); - - // URL para modo hub - let url_path = "index.html?view=chat&hub=true"; - - WebviewWindowBuilder::new( - app, - HUB_WINDOW_LABEL, - WebviewUrl::App(url_path.into()), - ) - .title("Chats de Suporte") - .inner_size(width, height) - .min_inner_size(200.0, 52.0) - .position(x, y) - .decorations(false) - .transparent(true) - .shadow(false) - .resizable(false) // Desabilitar redimensionamento manual - // Mantem o hub acessivel mesmo ao trocar de janela/app (skip_taskbar=true). - .always_on_top(true) - .skip_taskbar(true) - .focused(!start_minimized) - .visible(true) - .build() - .map_err(|e| e.to_string())?; - - // IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through) - if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) { - let _ = hub.set_ignore_cursor_events(false); - if !start_minimized { - let _ = hub.set_focus(); - } - } - - // REMOVIDO TEMPORARIAMENTE: set_hub_minimized logo apos build pode causar - // "resize em cima do resize" no timing errado do WebView2 - // let _ = set_hub_minimized(app, start_minimized); - - crate::log_info!("Hub window aberta (minimizada={})", start_minimized); - Ok(()) -} - -pub fn close_hub_window(app: &tauri::AppHandle) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) { - window.close().map_err(|e| e.to_string())?; - } - Ok(()) -} - -pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(), String> { - let _guard = WINDOW_OP_LOCK.lock(); - let window = app.get_webview_window(HUB_WINDOW_LABEL).ok_or("Hub window não encontrada")?; - - let (width, height) = if minimized { - (200.0, 52.0) // Chip minimizado - } else { - (400.0, 520.0) // Lista expandida (igual ao web) - }; - - let (x, y) = resolve_chat_window_position(app, Some(&window), width, height); - - // IGUAL AO CHAT: primeiro size, depois position (ordem importa para hit-test no Windows) - window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?; - window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?; - - // Foco apenas quando expandir (evita roubar foco ao minimizar apos abrir um chat). - if !minimized { - let _ = window.set_focus(); - } - - crate::log_info!("Hub -> minimized={}, size={}x{}, pos=({},{})", minimized, width, height, x, y); - Ok(()) -} diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs deleted file mode 100644 index 5e3939e..0000000 --- a/apps/desktop/src-tauri/src/lib.rs +++ /dev/null @@ -1,837 +0,0 @@ -mod agent; -mod chat; -#[cfg(target_os = "windows")] -mod rustdesk; -#[cfg(target_os = "windows")] -mod service_client; -mod usb_control; - -use agent::{collect_inventory_plain, collect_profile, AgentRuntime, MachineProfile}; -use chat::{ChatRuntime, ChatSession, ChatMessagesResponse, SendMessageResponse}; -use chrono::Local; -use usb_control::{UsbPolicy, UsbPolicyResult}; -use tauri::{Emitter, Listener, Manager, WindowEvent}; -use tauri_plugin_store::Builder as StorePluginBuilder; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::OnceLock; -#[cfg(target_os = "windows")] -use tauri::menu::{MenuBuilder, MenuItemBuilder}; -#[cfg(target_os = "windows")] -use tauri::tray::TrayIconBuilder; -#[cfg(target_os = "windows")] -use winreg::enums::*; -#[cfg(target_os = "windows")] -use winreg::RegKey; - -const DEFAULT_CONVEX_URL: &str = "https://convex.esdrasrenan.com.br"; - -// ============================================================================ -// Sistema de Logging para Agente -// ============================================================================ - -static AGENT_LOG_FILE: OnceLock> = OnceLock::new(); - -pub fn init_agent_logging() -> Result<(), String> { - let dir = logs_directory() - .ok_or("LOCALAPPDATA indisponivel para logging")?; - - std::fs::create_dir_all(&dir) - .map_err(|e| format!("Falha ao criar diretorio de logs: {e}"))?; - - let path = dir.join("raven-agent.log"); - let file = OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .map_err(|e| format!("Falha ao abrir raven-agent.log: {e}"))?; - - let _ = AGENT_LOG_FILE.set(std::sync::Mutex::new(file)); - Ok(()) -} - -pub fn log_agent(level: &str, message: &str) { - let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); - let line = format!("[{timestamp}] [{level}] {message}\n"); - - // Escreve para stderr (util em dev/debug) - eprint!("{line}"); - - // Escreve para arquivo - if let Some(mutex) = AGENT_LOG_FILE.get() { - if let Ok(mut file) = mutex.lock() { - let _ = file.write_all(line.as_bytes()); - let _ = file.flush(); - } - } -} - -#[macro_export] -macro_rules! log_info { - ($($arg:tt)*) => { - $crate::log_agent("INFO", format!($($arg)*).as_str()) - }; -} - -#[macro_export] -macro_rules! log_error { - ($($arg:tt)*) => { - $crate::log_agent("ERROR", format!($($arg)*).as_str()) - }; -} - -#[macro_export] -macro_rules! log_warn { - ($($arg:tt)*) => { - $crate::log_agent("WARN", format!($($arg)*).as_str()) - }; -} - -#[derive(Debug, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RustdeskProvisioningResult { - pub id: String, - pub password: String, - pub installed_version: Option, - pub updated: bool, - pub last_provisioned_at: i64, -} - -#[tauri::command] -fn collect_machine_profile() -> Result { - collect_profile().map_err(|error| error.to_string()) -} - -#[tauri::command] -fn collect_machine_inventory() -> Result { - Ok(collect_inventory_plain()) -} - -#[tauri::command] -fn start_machine_agent( - state: tauri::State, - base_url: String, - token: String, - status: Option, - interval_seconds: Option, -) -> Result<(), String> { - state - .start_heartbeat(base_url, token, status, interval_seconds) - .map_err(|error| error.to_string()) -} - -#[tauri::command] -fn stop_machine_agent(state: tauri::State) -> Result<(), String> { - state.stop(); - Ok(()) -} - -#[tauri::command] -fn open_devtools(window: tauri::WebviewWindow) -> Result<(), String> { - window.open_devtools(); - Ok(()) -} - -#[tauri::command] -fn log_app_event(message: String) -> Result<(), String> { - append_app_log(&message) -} - -fn append_app_log(message: &str) -> Result<(), String> { - let Some(dir) = logs_directory() else { - return Err("LOCALAPPDATA indisponivel para gravar logs".to_string()); - }; - - std::fs::create_dir_all(&dir) - .map_err(|error| format!("Falha ao criar pasta de logs: {error}"))?; - - let path = dir.join("app.log"); - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .map_err(|error| format!("Falha ao abrir app.log: {error}"))?; - - let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S"); - writeln!(file, "[{timestamp}] {message}") - .map_err(|error| format!("Falha ao escrever log: {error}"))?; - - Ok(()) -} - -fn logs_directory() -> Option { - let base = std::env::var("LOCALAPPDATA").ok()?; - Some(Path::new(&base).join("br.com.esdrasrenan.sistemadechamados").join("logs")) -} - -#[tauri::command] -async fn ensure_rustdesk_and_emit( - app: tauri::AppHandle, - config_string: Option, - password: Option, - machine_id: Option, -) -> Result { - let result = tauri::async_runtime::spawn_blocking(move || { - run_rustdesk_ensure(config_string, password, machine_id) - }) - .await - .map_err(|error| error.to_string())??; - - if let Err(error) = app.emit("raven://remote-access/provisioned", &result) { - eprintln!("[rustdesk] falha ao emitir evento raven://remote-access/provisioned: {error}"); - } - - Ok(result) -} - -#[cfg(target_os = "windows")] -fn run_rustdesk_ensure( - config_string: Option, - password: Option, - machine_id: Option, -) -> Result { - // Tenta usar o servico primeiro (sem UAC) - if service_client::is_service_available() { - log_info!("Usando Raven Service para provisionar RustDesk"); - match service_client::provision_rustdesk( - config_string.as_deref(), - password.as_deref(), - machine_id.as_deref(), - ) { - Ok(result) => { - return Ok(RustdeskProvisioningResult { - id: result.id, - password: result.password, - installed_version: result.installed_version, - updated: result.updated, - last_provisioned_at: result.last_provisioned_at, - }); - } - Err(e) => { - log_warn!("Falha ao usar servico para RustDesk: {e}"); - // Continua para fallback - } - } - } - - // Fallback: chamada direta (pode pedir UAC) - log_info!("Usando chamada direta para provisionar RustDesk (pode pedir UAC)"); - rustdesk::ensure_rustdesk( - config_string.as_deref(), - password.as_deref(), - machine_id.as_deref(), - ) - .map_err(|error| error.to_string()) -} - -#[cfg(not(target_os = "windows"))] -fn run_rustdesk_ensure( - _config_string: Option, - _password: Option, - _machine_id: Option, -) -> Result { - Err("Provisionamento automático do RustDesk está disponível apenas no Windows.".to_string()) -} - -#[tauri::command] -fn apply_usb_policy(policy: String) -> Result { - // Valida a politica primeiro - let _policy_enum = UsbPolicy::from_str(&policy) - .ok_or_else(|| format!("Politica USB invalida: {}. Use ALLOW, BLOCK_ALL ou READONLY.", policy))?; - - // Tenta usar o servico primeiro (sem UAC) - #[cfg(target_os = "windows")] - if service_client::is_service_available() { - log_info!("Usando Raven Service para aplicar politica USB: {}", policy); - match service_client::apply_usb_policy(&policy) { - Ok(result) => { - return Ok(UsbPolicyResult { - success: result.success, - policy: result.policy, - error: result.error, - applied_at: result.applied_at, - }); - } - Err(e) => { - log_warn!("Falha ao usar servico para USB policy: {e}"); - // Continua para fallback - } - } - } - - // Fallback: chamada direta (pode pedir UAC) - log_info!("Usando chamada direta para aplicar politica USB (pode pedir UAC)"); - usb_control::apply_usb_policy(_policy_enum).map_err(|e| e.to_string()) -} - -#[tauri::command] -fn get_usb_policy() -> Result { - // Tenta usar o servico primeiro - #[cfg(target_os = "windows")] - if service_client::is_service_available() { - match service_client::get_usb_policy() { - Ok(policy) => return Ok(policy), - Err(e) => { - log_warn!("Falha ao obter USB policy via servico: {e}"); - // Continua para fallback - } - } - } - - // Fallback: leitura direta (nao precisa elevacao para ler) - usb_control::get_current_policy() - .map(|p| p.as_str().to_string()) - .map_err(|e| e.to_string()) -} - -#[tauri::command] -fn refresh_usb_policy() -> Result<(), String> { - usb_control::refresh_group_policy().map_err(|e| e.to_string()) -} - -// ============================================================================ -// COMANDOS DE CHAT -// ============================================================================ - -#[tauri::command] -fn start_chat_polling( - state: tauri::State, - app: tauri::AppHandle, - base_url: String, - convex_url: Option, - token: String, -) -> Result<(), String> { - let url = convex_url.unwrap_or_else(|| DEFAULT_CONVEX_URL.to_string()); - state.start_polling(base_url, url, token, app) -} - -#[tauri::command] -fn stop_chat_polling(state: tauri::State) -> Result<(), String> { - state.stop(); - Ok(()) -} - -#[tauri::command] -fn is_chat_using_realtime(state: tauri::State) -> bool { - state.is_using_sse() -} - -#[tauri::command] -fn get_chat_sessions(state: tauri::State) -> Vec { - state.get_sessions() -} - -#[tauri::command] -async fn fetch_chat_sessions(base_url: String, token: String) -> Result, String> { - chat::fetch_sessions(&base_url, &token).await -} - -#[tauri::command] -async fn fetch_chat_messages( - base_url: String, - token: String, - ticket_id: String, - since: Option, -) -> Result { - chat::fetch_messages(&base_url, &token, &ticket_id, since).await -} - -#[tauri::command] -async fn send_chat_message( - base_url: String, - token: String, - ticket_id: String, - body: String, - attachments: Option>, -) -> Result { - chat::send_message(&base_url, &token, &ticket_id, &body, attachments).await -} - -#[tauri::command] -async fn mark_chat_messages_read( - base_url: String, - token: String, - ticket_id: String, - message_ids: Vec, -) -> Result<(), String> { - if message_ids.is_empty() { - return Ok(()); - } - chat::mark_messages_read(&base_url, &token, &ticket_id, &message_ids).await -} - -#[tauri::command] -async fn upload_chat_file( - base_url: String, - token: String, - file_path: String, -) -> Result { - use std::path::Path; - - // Ler o arquivo - let path = Path::new(&file_path); - let file_name = path - .file_name() - .and_then(|n| n.to_str()) - .ok_or("Nome de arquivo inválido")? - .to_string(); - - let file_data = std::fs::read(&file_path) - .map_err(|e| format!("Falha ao ler arquivo: {e}"))?; - - let file_size = file_data.len() as u64; - - // Validar arquivo - chat::is_allowed_file(&file_name, file_size)?; - - // Obter tipo MIME - let mime_type = chat::get_mime_type(&file_name); - - // Gerar URL de upload - let upload_url = chat::generate_upload_url( - &base_url, - &token, - &file_name, - &mime_type, - file_size, - ) - .await?; - - // Fazer upload - let storage_id = chat::upload_file(&upload_url, file_data, &mime_type).await?; - - Ok(chat::AttachmentPayload { - storage_id, - name: file_name, - size: Some(file_size), - mime_type: Some(mime_type), - }) -} - -#[tauri::command] -async fn open_chat_window(app: tauri::AppHandle, ticket_id: String, ticket_ref: u64) -> Result<(), String> { - log_info!("[CMD] open_chat_window called: ticket_id={}, ticket_ref={}", ticket_id, ticket_ref); - let app_handle = app.clone(); - let ticket_id_for_task = ticket_id.clone(); - let result = tauri::async_runtime::spawn_blocking(move || { - chat::open_chat_window(&app_handle, &ticket_id_for_task, ticket_ref) - }) - .await - .map_err(|err| format!("Falha ao abrir chat (join): {err}"))?; - log_info!("[CMD] open_chat_window result: {:?}", result); - result -} - -#[tauri::command] -fn close_chat_window(app: tauri::AppHandle, ticket_id: String) -> Result<(), String> { - chat::close_chat_window(&app, &ticket_id) -} - -#[tauri::command] -fn minimize_chat_window(app: tauri::AppHandle, ticket_id: String) -> Result<(), String> { - chat::minimize_chat_window(&app, &ticket_id) -} - -#[tauri::command] -fn set_chat_minimized(app: tauri::AppHandle, ticket_id: String, minimized: bool) -> Result<(), String> { - chat::set_chat_minimized(&app, &ticket_id, minimized) -} - -#[tauri::command] -async fn open_hub_window(app: tauri::AppHandle) -> Result<(), String> { - let app_handle = app.clone(); - tauri::async_runtime::spawn_blocking(move || { - chat::open_hub_window(&app_handle) - }) - .await - .map_err(|err| format!("Falha ao abrir hub (join): {err}"))? -} - -#[tauri::command] -fn close_hub_window(app: tauri::AppHandle) -> Result<(), String> { - chat::close_hub_window(&app) -} - -#[tauri::command] -fn set_hub_minimized(app: tauri::AppHandle, minimized: bool) -> Result<(), String> { - chat::set_hub_minimized(&app, minimized) -} - -// ============================================================================ -// Handler de Deep Link (raven://) -// ============================================================================ - -/// Processa URLs do protocolo raven:// -/// Formatos suportados: -/// - raven://ticket/{token} - Abre visualizacao do chamado -/// - raven://chat/{ticketId}?token={token} - Abre chat do chamado -/// - raven://rate/{token} - Abre avaliacao do chamado -fn handle_deep_link(app: &tauri::AppHandle, url: &str) { - log_info!("Processando deep link: {url}"); - - // Remove o prefixo raven:// - let path = url.trim_start_matches("raven://"); - - // Parse do path - let parts: Vec<&str> = path.split('/').collect(); - - if parts.is_empty() { - log_warn!("Deep link invalido: path vazio"); - return; - } - - match parts[0] { - "ticket" => { - if parts.len() > 1 { - let token = parts[1].split('?').next().unwrap_or(parts[1]); - log_info!("Abrindo ticket com token: {token}"); - - // Mostra a janela principal - if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.set_focus(); - - // Emite evento para o frontend navegar para o ticket - let _ = app.emit("raven://deep-link/ticket", serde_json::json!({ - "token": token - })); - } - } - } - "chat" => { - if parts.len() > 1 { - let ticket_id = parts[1].split('?').next().unwrap_or(parts[1]); - log_info!("Abrindo chat do ticket: {ticket_id}"); - - // Abre janela de chat (ticket_ref 0 quando vem de deeplink) - if let Err(e) = chat::open_chat_window(app, ticket_id, 0) { - log_error!("Falha ao abrir chat: {e}"); - } - } - } - "rate" => { - if parts.len() > 1 { - let token = parts[1].split('?').next().unwrap_or(parts[1]); - log_info!("Abrindo avaliacao com token: {token}"); - - // Mostra a janela principal - if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.set_focus(); - - // Emite evento para o frontend navegar para avaliacao - let _ = app.emit("raven://deep-link/rate", serde_json::json!({ - "token": token - })); - } - } - } - _ => { - log_warn!("Deep link desconhecido: {path}"); - } - } -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .manage(AgentRuntime::new()) - .manage(ChatRuntime::new()) - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_opener::init()) - .plugin(StorePluginBuilder::default().build()) - .plugin(tauri_plugin_updater::Builder::new().build()) - .plugin(tauri_plugin_process::init()) - .plugin(tauri_plugin_notification::init()) - .plugin(tauri_plugin_deep_link::init()) - .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { - // Quando uma segunda instância tenta iniciar, foca a janela existente - if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.unminimize(); - let _ = window.set_focus(); - } - })) - .on_window_event(|window, event| { - if let WindowEvent::CloseRequested { api, .. } = event { - api.prevent_close(); - let _ = window.hide(); - } - }) - .setup(|app| { - // Inicializa sistema de logging primeiro - if let Err(e) = init_agent_logging() { - eprintln!("[raven] Falha ao inicializar logging: {e}"); - } - - log_info!("Raven iniciando..."); - - // Configura handler de deep link (raven://) - #[cfg(desktop)] - { - let handle = app.handle().clone(); - app.listen("deep-link://new-url", move |event| { - let urls = event.payload(); - log_info!("Deep link recebido: {urls}"); - handle_deep_link(&handle, urls); - }); - } - - #[cfg(target_os = "windows")] - { - let start_in_background = std::env::args().any(|arg| arg == "--background"); - setup_raven_autostart(); - setup_tray(app.handle())?; - if start_in_background { - if let Some(win) = app.get_webview_window("main") { - let _ = win.hide(); - } - } - - // Tenta iniciar o agente e chat em background se houver credenciais salvas - let app_handle = app.handle().clone(); - let agent_runtime = app.state::().inner().clone(); - let chat_runtime = app.state::().inner().clone(); - tauri::async_runtime::spawn(async move { - // Aguarda um pouco para o app estabilizar - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; - if let Err(e) = try_start_background_agent(&app_handle, agent_runtime, chat_runtime).await { - log_warn!("Agente nao iniciado em background: {e}"); - } - }); - } - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - collect_machine_profile, - collect_machine_inventory, - start_machine_agent, - stop_machine_agent, - open_devtools, - log_app_event, - ensure_rustdesk_and_emit, - apply_usb_policy, - get_usb_policy, - refresh_usb_policy, - // Chat commands - start_chat_polling, - stop_chat_polling, - is_chat_using_realtime, - get_chat_sessions, - fetch_chat_sessions, - fetch_chat_messages, - send_chat_message, - mark_chat_messages_read, - upload_chat_file, - open_chat_window, - close_chat_window, - minimize_chat_window, - set_chat_minimized, - // Hub commands - open_hub_window, - close_hub_window, - set_hub_minimized - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} - -#[cfg(target_os = "windows")] -fn setup_raven_autostart() { - let exe_path = match std::env::current_exe() { - Ok(p) => p, - Err(e) => { - log_error!("Falha ao obter caminho do executavel: {e}"); - return; - } - }; - - let path_str = exe_path.display().to_string(); - // Adiciona flag --background para indicar inicio automatico - let value = format!("\"{}\" --background", path_str); - - let hkcu = RegKey::predef(HKEY_CURRENT_USER); - - let key = match hkcu.create_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run") { - Ok((key, _)) => key, - Err(e) => { - log_error!("Falha ao criar/abrir chave de registro Run: {e}"); - return; - } - }; - - if let Err(e) = key.set_value("Raven", &value) { - log_error!("Falha ao definir valor de auto-start no registro: {e}"); - return; - } - - log_info!("Auto-start configurado: {value}"); - - // Valida que foi salvo corretamente - match key.get_value::("Raven") { - Ok(saved) => { - if saved == value { - log_info!("Auto-start validado: entrada existe no registro"); - } else { - log_warn!("Auto-start: valor difere. Esperado: {value}, Salvo: {saved}"); - } - } - Err(e) => { - log_warn!("Auto-start: nao foi possivel validar entrada: {e}"); - } - } -} - -#[cfg(target_os = "windows")] -fn setup_tray(app: &tauri::AppHandle) -> tauri::Result<()> { - let show_item = MenuItemBuilder::with_id("show", "Mostrar").build(app)?; - let chat_item = MenuItemBuilder::with_id("chat", "Abrir Chat").build(app)?; - let quit_item = MenuItemBuilder::with_id("quit", "Sair").build(app)?; - let menu = MenuBuilder::new(app) - .items(&[&show_item, &chat_item, &quit_item]) - .build()?; - - let mut builder = TrayIconBuilder::new() - .menu(&menu) - .on_menu_event(|tray, event| { - match event.id().as_ref() { - "show" => { - if let Some(win) = tray.app_handle().get_webview_window("main") { - let _ = win.show(); - let _ = win.set_focus(); - } - // Reabrir chat se houver sessao ativa - if let Some(chat_runtime) = tray.app_handle().try_state::() { - let sessions = chat_runtime.get_sessions(); - if let Some(session) = sessions.first() { - let _ = chat::open_chat_window(tray.app_handle(), &session.ticket_id, session.ticket_ref); - } - } - } - "chat" => { - // Abrir janela de chat se houver sessao ativa - if let Some(chat_runtime) = tray.app_handle().try_state::() { - let sessions = chat_runtime.get_sessions(); - if sessions.len() > 1 { - // Multiplas sessoes - abrir hub - if let Err(e) = chat::open_hub_window(tray.app_handle()) { - log_error!("Falha ao abrir hub de chat: {e}"); - } - } else if let Some(session) = sessions.first() { - // Uma sessao - abrir diretamente - if let Err(e) = chat::open_chat_window(tray.app_handle(), &session.ticket_id, session.ticket_ref) { - log_error!("Falha ao abrir janela de chat: {e}"); - } - } - } - } - "quit" => { - tray.app_handle().exit(0); - } - _ => {} - } - }) - .on_tray_icon_event(|tray, event| { - if let tauri::tray::TrayIconEvent::DoubleClick { .. } = event { - if let Some(win) = tray.app_handle().get_webview_window("main") { - let _ = win.show(); - let _ = win.set_focus(); - } - // Reabrir chat se houver sessao ativa - if let Some(chat_runtime) = tray.app_handle().try_state::() { - let sessions = chat_runtime.get_sessions(); - if let Some(session) = sessions.first() { - let _ = chat::open_chat_window(tray.app_handle(), &session.ticket_id, session.ticket_ref); - } - } - } - }); - - if let Some(icon) = app.default_window_icon() { - builder = builder.icon(icon.clone()); - } - - builder = builder.tooltip("Raven"); - - builder.build(app)?; - Ok(()) -} - -#[cfg(target_os = "windows")] -async fn try_start_background_agent( - app: &tauri::AppHandle, - agent_runtime: AgentRuntime, - chat_runtime: ChatRuntime, -) -> Result<(), String> { - log_info!("Verificando credenciais salvas para iniciar agente..."); - - let app_data = app - .path() - .app_local_data_dir() - .map_err(|e| format!("Falha ao obter diretorio de dados: {e}"))?; - - let store_path = app_data.join("machine-agent.json"); - - if !store_path.exists() { - return Err("Nenhuma configuracao encontrada".to_string()); - } - - // Ler arquivo JSON diretamente - let content = std::fs::read_to_string(&store_path) - .map_err(|e| format!("Falha ao ler machine-agent.json: {e}"))?; - - let data: serde_json::Value = serde_json::from_str(&content) - .map_err(|e| format!("Falha ao parsear machine-agent.json: {e}"))?; - - let token = data - .get("token") - .and_then(|v| v.as_str()) - .filter(|t| !t.is_empty()) - .ok_or("Token nao encontrado ou vazio")?; - - let config = data.get("config"); - - let api_base_url = config - .and_then(|c| c.get("apiBaseUrl")) - .and_then(|v| v.as_str()) - .unwrap_or("https://tickets.esdrasrenan.com.br"); - - let convex_url = config - .and_then(|c| c.get("convexUrl")) - .and_then(|v| v.as_str()) - .unwrap_or(DEFAULT_CONVEX_URL); - - let interval = config - .and_then(|c| c.get("heartbeatIntervalSec")) - .and_then(|v| v.as_u64()) - .unwrap_or(300); - - log_info!( - "Iniciando agente em background: url={}, interval={}s", - api_base_url, - interval - ); - - agent_runtime - .start_heartbeat( - api_base_url.to_string(), - token.to_string(), - Some("online".to_string()), - Some(interval), - ) - .map_err(|e| format!("Falha ao iniciar heartbeat: {e}"))?; - - // Iniciar sistema de chat (WebSocket + fallback HTTP polling) - if let Err(e) = - chat_runtime.start_polling(api_base_url.to_string(), convex_url.to_string(), token.to_string(), app.clone()) - { - log_warn!("Falha ao iniciar chat em background: {e}"); - } else { - log_info!("Chat iniciado com sucesso (Convex WebSocket)"); - } - - log_info!("Agente iniciado com sucesso em background"); - - Ok(()) -} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs deleted file mode 100644 index abafa26..0000000 --- a/apps/desktop/src-tauri/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -fn main() { - appsdesktop_lib::run() -} diff --git a/apps/desktop/src-tauri/src/rustdesk.rs b/apps/desktop/src-tauri/src/rustdesk.rs deleted file mode 100644 index 8c6cee4..0000000 --- a/apps/desktop/src-tauri/src/rustdesk.rs +++ /dev/null @@ -1,1588 +0,0 @@ -use crate::RustdeskProvisioningResult; -use chrono::{Local, Utc}; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use reqwest::blocking::Client; -use serde::Deserialize; -use serde_json::{Map as JsonMap, Value as JsonValue}; -use sha2::{Digest, Sha256}; -use std::env; -use std::ffi::OsStr; -use std::fs::{self, File, OpenOptions}; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use std::thread; -use std::time::Duration; -use thiserror::Error; -use std::os::windows::process::CommandExt; - -const RELEASES_API: &str = "https://api.github.com/repos/rustdesk/rustdesk/releases/latest"; -const USER_AGENT: &str = "RavenDesktop/1.0"; -const SERVER_HOST: &str = "rust.rever.com.br"; -const SERVER_KEY: &str = "0mxocQKmK6GvTZQYKgjrG9tlNkKOqf81gKgqwAmnZuI="; -const DEFAULT_PASSWORD: &str = "FMQ9MA>e73r.FI> = Lazy::new(|| Mutex::new(())); - -#[derive(Debug, Error)] -pub enum RustdeskError { - #[error("HTTP error: {0}")] - Http(#[from] reqwest::Error), - #[error("I/O error: {0}")] - Io(#[from] io::Error), - #[error("Release asset não encontrado para Windows x86_64")] - AssetMissing, - #[error("Falha ao executar comando {command}: status {status:?}")] - CommandFailed { command: String, status: Option }, - #[error("Falha ao detectar ID do RustDesk")] - MissingId, -} - -#[derive(Debug, Deserialize)] -struct ReleaseAsset { - name: String, - browser_download_url: String, -} - -#[derive(Debug, Deserialize)] -struct ReleaseResponse { - tag_name: String, - assets: Vec, -} - -/// Auxiliar para definir ID customizado baseado no machine_id -fn define_custom_id_from_machine(exe_path: &Path, machine_id: Option<&str>) -> Option { - if let Some(value) = machine_id.and_then(|raw| { - let trimmed = raw.trim(); - if trimmed.is_empty() { None } else { Some(trimmed) } - }) { - match set_custom_id(exe_path, value) { - Ok(custom) => { - log_event(format!("ID determinístico definido: {custom}")); - Some(custom) - } - Err(error) => { - log_event(format!("Falha ao definir ID determinístico: {error}")); - None - } - } - } else { - None - } -} - -pub fn ensure_rustdesk( - config_string: Option<&str>, - password_override: Option<&str>, - machine_id: Option<&str>, -) -> Result { - let _guard = PROVISION_MUTEX.lock(); - log_event("Iniciando preparo do RustDesk"); - - if let Err(error) = ensure_service_profiles_writable_preflight() { - log_event(format!( - "Aviso: não foi possível preparar ACL dos perfis do serviço ({error}). Continuando mesmo assim; o serviço pode não aplicar a senha." - )); - } - - // IMPORTANTE: Ler o ID existente ANTES de qualquer limpeza - // Isso preserva o ID quando o Raven é reinstalado mas o RustDesk permanece - let preserved_remote_id = read_remote_id_from_profiles(); - if let Some(ref id) = preserved_remote_id { - log_event(format!("ID existente preservado antes da limpeza: {}", id)); - } - - let exe_path = detect_executable_path(); - let (installed_version, freshly_installed) = ensure_installed(&exe_path)?; - log_event(if freshly_installed { - "RustDesk instalado a partir do instalador mais recente" - } else { - "RustDesk já instalado, usando binário existente" - }); - - match stop_rustdesk_processes() { - Ok(_) => log_event("Instâncias existentes do RustDesk encerradas"), - Err(error) => log_event(format!( - "Aviso: não foi possível parar completamente o RustDesk antes da reprovisionamento ({error})" - )), - } - - // So limpa perfis se for instalacao fresca (RustDesk nao existia) - // Se ja existia, preservamos o ID para manter consistencia - if freshly_installed { - match purge_existing_rustdesk_profiles() { - Ok(_) => log_event("Configurações antigas do RustDesk limpas (instalação fresca)"), - Err(error) => log_event(format!( - "Aviso: não foi possível limpar completamente os perfis existentes do RustDesk ({error})" - )), - } - } else { - log_event("Mantendo perfis existentes do RustDesk (preservando ID)"); - } - - if let Some(value) = config_string.and_then(|raw| { - let trimmed = raw.trim(); - if trimmed.is_empty() { None } else { Some(trimmed) } - }) { - if let Err(error) = run_with_args(&exe_path, &["--config", value]) { - log_event(format!("Falha ao aplicar configuração inline: {error}")); - } else { - log_event("Configuração aplicada via --config"); - } - } else { - let config_path = write_config_files()?; - log_event(format!( - "Arquivo de configuração atualizado em {}", - config_path.display() - )); - - if let Err(error) = apply_config(&exe_path, &config_path) { - log_event(format!("Falha ao aplicar configuração via CLI: {error}")); - } else { - log_event("Configuração aplicada via CLI"); - } - } - - let password = password_override - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()) - .unwrap_or_else(|| DEFAULT_PASSWORD.to_string()); - - if let Err(error) = set_password(&exe_path, &password) { - log_event(format!("Falha ao definir senha padrão: {error}")); - } else { - log_event("Senha padrão definida com sucesso"); - log_event("Aplicando senha nos perfis do RustDesk"); - match ensure_password_files(&password) { - Ok(_) => { - log_event("Senha e flags de segurança gravadas em todos os perfis do RustDesk"); - log_password_replication(&password); - } - Err(error) => log_event(format!("Falha ao persistir senha nos perfis: {error}")), - } - - match propagate_password_profile() { - Ok(_) => log_event("Perfil base propagado para ProgramData e perfis de serviço"), - Err(error) => log_event(format!("Falha ao copiar perfil de senha: {error}")), - } - - match replicate_password_artifacts() { - Ok(_) => log_event("Artefatos de senha replicados para o serviço do RustDesk"), - Err(error) => log_event(format!("Falha ao replicar artefatos de senha: {error}")), - } - - if let Err(error) = enforce_security_flags() { - log_event(format!("Falha ao reforçar configuração de senha permanente: {error}")); - } - } - - // Se ja existe um ID preservado E o RustDesk nao foi recem-instalado, usa o ID existente - // Isso garante que reinstalar o Raven nao muda o ID do RustDesk - let custom_id = if let Some(ref existing_id) = preserved_remote_id { - if !freshly_installed { - log_event(format!("Reutilizando ID existente do RustDesk: {}", existing_id)); - Some(existing_id.clone()) - } else { - // Instalacao fresca - define novo ID baseado no machine_id - define_custom_id_from_machine(&exe_path, machine_id) - } - } else { - // Sem ID preservado - define novo ID baseado no machine_id - define_custom_id_from_machine(&exe_path, machine_id) - }; - - if let Err(error) = ensure_service_running(&exe_path) { - log_event(format!("Falha ao reiniciar serviço do RustDesk: {error}")); - } else { - log_event("Serviço RustDesk reiniciado/run ativo"); - } - - let reported_id = match query_id_with_retries(&exe_path, 5) { - Ok(value) => value, - Err(error) => { - log_event(format!("Falha ao obter ID após múltiplas tentativas: {error}")); - match read_remote_id_from_profiles().or_else(|| custom_id.clone()) { - Some(value) => { - log_event(format!("ID obtido via arquivos de perfil: {value}")); - value - } - None => return Err(error), - } - } - }; - - let mut final_id = reported_id.clone(); - - if let Some(expected) = custom_id.as_ref() { - if expected != &reported_id { - log_event(format!( - "ID retornado difere do determinístico ({expected}) -> reaplicando ID determinístico" - )); - - let mut enforced = false; - - match set_custom_id(&exe_path, expected) { - Ok(_) => match query_id_with_retries(&exe_path, 3) { - Ok(rechecked) => { - if &rechecked == expected { - log_event(format!("ID determinístico aplicado com sucesso: {rechecked}")); - final_id = rechecked; - enforced = true; - } else { - log_event(format!( - "ID ainda difere após reaplicação (esperado {expected}, reportado {rechecked}); usando ID reportado" - )); - final_id = rechecked; - } - } - Err(error) => { - log_event(format!( - "Falha ao consultar ID após reaplicação: {error}; usando ID reportado ({reported_id})" - )); - final_id = reported_id.clone(); - } - }, - Err(error) => { - log_event(format!( - "Falha ao reaplicar ID determinístico ({expected}): {error}; usando ID reportado ({reported_id})" - )); - final_id = reported_id.clone(); - } - } - - if !enforced && final_id != *expected { - log_event("Aviso: não foi possível aplicar o ID determinístico; manteremos o ID real fornecido pelo serviço"); - } - } - } - - ensure_remote_id_files(&final_id); - - let version = query_version(&exe_path).ok().or(installed_version); - - let last_provisioned_at = Utc::now().timestamp_millis(); - let result = RustdeskProvisioningResult { - id: final_id.clone(), - password: password.clone(), - installed_version: version.clone(), - updated: freshly_installed, - last_provisioned_at, - }; - - // Salva os dados do RustDesk diretamente no arquivo machine-agent.json - // para evitar conflitos com o Tauri Store do TypeScript - let rustdesk_data = serde_json::json!({ - "id": final_id, - "password": password, - "installedVersion": version, - "updated": freshly_installed, - "lastProvisionedAt": last_provisioned_at, - "lastSyncedAt": serde_json::Value::Null, - "lastError": serde_json::Value::Null - }); - if let Err(error) = upsert_machine_store_value("rustdesk", rustdesk_data) { - log_event(format!("Aviso: falha ao salvar dados do RustDesk no store: {error}")); - } else { - log_event("Dados do RustDesk salvos no machine-agent.json"); - } - - // Sincroniza com o backend imediatamente apos provisionar - // O Rust faz o HTTP direto, sem passar pelo CSP do webview - if let Err(error) = sync_remote_access_with_backend(&result) { - log_event(format!("Aviso: falha ao sincronizar com backend: {error}")); - } else { - log_event("Acesso remoto sincronizado com backend"); - // Atualiza lastSyncedAt no store - let synced_data = serde_json::json!({ - "id": final_id, - "password": password, - "installedVersion": version, - "updated": freshly_installed, - "lastProvisionedAt": last_provisioned_at, - "lastSyncedAt": Utc::now().timestamp_millis(), - "lastError": serde_json::Value::Null - }); - if let Err(e) = upsert_machine_store_value("rustdesk", synced_data) { - log_event(format!("Aviso: falha ao atualizar lastSyncedAt: {e}")); - } else { - log_event("lastSyncedAt atualizado com sucesso"); - } - } - - log_event(format!("Provisionamento concluído. ID final: {final_id}. Versão: {:?}", version)); - - Ok(result) -} - -fn detect_executable_path() -> PathBuf { - let program_files = env::var("PROGRAMFILES").unwrap_or_else(|_| "C:/Program Files".to_string()); - Path::new(&program_files).join("RustDesk").join("rustdesk.exe") -} - -fn ensure_installed(exe_path: &Path) -> Result<(Option, bool), RustdeskError> { - if exe_path.exists() { - return Ok((None, false)); - } - - let cache_root = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())) - .join(CACHE_DIR_NAME); - fs::create_dir_all(&cache_root)?; - - let (installer_path, version_tag) = download_latest_installer(&cache_root)?; - run_installer(&installer_path)?; - thread::sleep(Duration::from_secs(20)); - - Ok((Some(version_tag), true)) -} - -fn download_latest_installer(cache_root: &Path) -> Result<(PathBuf, String), RustdeskError> { - let client = Client::builder() - .user_agent(USER_AGENT) - .timeout(Duration::from_secs(60)) - .build()?; - let release: ReleaseResponse = client.get(RELEASES_API).send()?.error_for_status()?.json()?; - let asset = release - .assets - .iter() - .find(|a| a.name.ends_with("x86_64.exe")) - .ok_or(RustdeskError::AssetMissing)?; - let target_path = cache_root.join(&asset.name); - if target_path.exists() { - return Ok((target_path, release.tag_name)); - } - - let mut response = client.get(&asset.browser_download_url).send()?.error_for_status()?; - let mut output = File::create(&target_path)?; - response.copy_to(&mut output)?; - Ok((target_path, release.tag_name)) -} - -fn run_installer(installer_path: &Path) -> Result<(), RustdeskError> { - let status = hidden_command(installer_path) - .arg("--silent-install") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --silent-install", installer_path.display()), - status: status.code(), - }); - } - Ok(()) -} - -fn write_config_files() -> Result { - let config_contents = build_config_contents(); - let main_path = program_data_config_dir().join("RustDesk2.toml"); - write_file(&main_path, &config_contents)?; - log_event(format!( - "Config principal gravada em {}", - main_path.display() - )); - - let _ = ensure_service_profiles_writable_preflight(); - for service_dir in service_profile_dirs() { - let service_profile = service_dir.join("RustDesk2.toml"); - if let Err(error) = write_file(&service_profile, &config_contents) { - log_event(format!( - "Falha ao gravar config no perfil do serviço ({}): {error}", - service_profile.display() - )); - } - } - - if let Some(appdata_path) = user_appdata_config_path("RustDesk2.toml") { - if let Err(error) = write_file(&appdata_path, &config_contents) { - log_event(format!( - "Falha ao atualizar config no AppData do usuário: {error}" - )); - } - } - - Ok(main_path) -} - -fn write_file(path: &Path, contents: &str) -> Result<(), io::Error> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(contents.as_bytes()) -} - -fn program_data_config_dir() -> PathBuf { - PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())) - .join("RustDesk") - .join("config") -} - -fn user_appdata_config_dir() -> Option { - env::var("APPDATA") - .ok() - .map(|value| Path::new(&value).join("RustDesk").join("config")) -} - -fn user_appdata_config_path(filename: &str) -> Option { - user_appdata_config_dir().map(|dir| dir.join(filename)) -} - -fn build_config_contents() -> String { - format!( - r#"[options] -key = "{key}" -relay-server = "{host}" -custom-rendezvous-server = "{host}" -api-server = "https://{host}" -verification-method = "{verification}" -approve-mode = "{approve}" -"#, - host = SERVER_HOST, - key = SERVER_KEY, - verification = SECURITY_VERIFICATION_VALUE, - approve = SECURITY_APPROVE_MODE_VALUE, - ) -} - -fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError> { - let status = hidden_command(exe_path) - .arg("--import-config") - .arg(config_path) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --import-config {}", exe_path.display(), config_path.display()), - status: status.code(), - }); - } - Ok(()) -} - -fn set_password(exe_path: &Path, secret: &str) -> Result<(), RustdeskError> { - run_with_args(exe_path, &["--password", secret]) -} - -fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result { - let custom_id = derive_numeric_id(machine_id); - run_with_args(exe_path, &["--set-id", &custom_id])?; - Ok(custom_id) -} - -fn derive_numeric_id(machine_id: &str) -> String { - let mut hasher = Sha256::new(); - hasher.update(machine_id.as_bytes()); - let hash = hasher.finalize(); - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&hash[..8]); - let value = u64::from_le_bytes(bytes); - let num = (value % 900_000_000) + 100_000_000; - format!("{:09}", num) -} - -fn ensure_service_running(exe_path: &Path) -> Result<(), RustdeskError> { - ensure_service_installed(exe_path)?; - - if let Err(error) = configure_service_startup() { - log_event(format!( - "Aviso: não foi possível reforçar autostart/recuperação do serviço RustDesk: {error}" - )); - } - - fn start_sequence() -> Result<(), RustdeskError> { - let _ = run_sc(&["stop", SERVICE_NAME]); - thread::sleep(Duration::from_secs(2)); - let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]); - run_sc(&["start", SERVICE_NAME]) - } - - let _ = match start_sequence() { - Ok(_) => Ok(()), - Err(RustdeskError::CommandFailed { command: _, status: Some(5), .. }) => { - log_event("SC retornou acesso negado; tentando ajustar ACL dos perfis do serviço..."); - ensure_service_profiles_writable_preflight().map_err(|error| RustdeskError::CommandFailed { - command: format!("fix_acl ({error})"), - status: Some(5), - })?; - let _ = run_sc(&["stop", SERVICE_NAME]); - let _ = start_sequence(); - Ok(()) - } - Err(error) => Err(error), - }; - - remove_rustdesk_autorun_artifacts(); - - // Revalida se o serviço realmente subiu; se não, reinstala e tenta novamente. - match query_service_state() { - Some(state) if state.eq_ignore_ascii_case("running") => Ok(()), - _ => { - log_event("Serviço RustDesk não está em execução após tentativa de start; reaplicando --install-service e start"); - let _ = run_with_args(exe_path, &["--install-service"]); - let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]); - if let Err(error) = start_sequence() { - log_event(format!( - "Falha ao subir o serviço RustDesk mesmo após reinstalação: {error}" - )); - } - Ok(()) - } - } -} - -fn configure_service_startup() -> Result<(), RustdeskError> { - let start_arg = format!("start= {}", "auto"); - run_sc(&["config", SERVICE_NAME, &start_arg])?; - - let reset_arg = format!("reset= {}", "86400"); - let actions_arg = "actions= restart/5000/restart/5000/restart/5000"; - let failure_actions_applied = run_sc(&["failure", SERVICE_NAME, &reset_arg, actions_arg]).is_ok(); - let _ = run_sc(&["failureflag", SERVICE_NAME, "1"]); - - if failure_actions_applied { - log_event("Serviço RustDesk configurado para reiniciar automaticamente em caso de falha"); - } else { - log_event("Aviso: não foi possível configurar recuperação automática do serviço RustDesk"); - } - - Ok(()) -} - -fn query_service_state() -> Option { - let output = hidden_command("sc") - .args(["query", SERVICE_NAME]) - .output() - .ok()?; - if !output.status.success() { - return None; - } - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if let Some(pos) = line.find("STATE") { - // Example: " STATE : 4 RUNNING" - let state = line[pos..].to_string(); - if state.to_lowercase().contains("running") { - return Some("running".to_string()); - } - if state.to_lowercase().contains("stopped") { - return Some("stopped".to_string()); - } - } - } - None -} - -fn remove_rustdesk_autorun_artifacts() { - // Remove atalhos de inicialização automática para evitar abrir GUI a cada boot/login. - let mut startup_paths: Vec = Vec::new(); - if let Ok(appdata) = env::var("APPDATA") { - startup_paths.push( - Path::new(&appdata) - .join("Microsoft") - .join("Windows") - .join("Start Menu") - .join("Programs") - .join("Startup") - .join("RustDesk.lnk"), - ); - } - startup_paths.push( - Path::new("C:\\ProgramData") - .join("Microsoft") - .join("Windows") - .join("Start Menu") - .join("Programs") - .join("Startup") - .join("RustDesk.lnk"), - ); - - for path in startup_paths { - if path.exists() { - match fs::remove_file(&path) { - Ok(_) => log_event(format!("Atalho de inicialização do RustDesk removido: {}", path.display())), - Err(error) => log_event(format!( - "Falha ao remover atalho de inicialização do RustDesk ({}): {}", - path.display(), - error - )), - } - } - } - - for hive in ["HKCU", "HKLM"] { - let reg_path = format!(r"{}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", hive); - let status = hidden_command("reg") - .args(["delete", ®_path, "/v", "RustDesk", "/f"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - if let Ok(code) = status { - if code.success() { - log_event(format!("Entrada de auto-run RustDesk removida de {}", reg_path)); - } - } - } -} - -fn stop_rustdesk_processes() -> Result<(), RustdeskError> { - if let Err(error) = try_stop_service() { - log_event(format!( - "Não foi possível parar o serviço RustDesk antes da sincronização: {error}" - )); - } - - let status = hidden_command("taskkill") - .args(["/F", "/T", "/IM", "rustdesk.exe"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - - if status.success() || matches!(status.code(), Some(128)) { - Ok(()) - } else { - Err(RustdeskError::CommandFailed { - command: "taskkill /F /T /IM rustdesk.exe".into(), - status: status.code(), - }) - } -} - -fn try_stop_service() -> Result<(), RustdeskError> { - match run_sc(&["stop", SERVICE_NAME]) { - Ok(_) => { - thread::sleep(Duration::from_secs(2)); - Ok(()) - } - Err(RustdeskError::CommandFailed { status: Some(code), .. }) if code == 1060 || code == 1062 => Ok(()), - Err(RustdeskError::CommandFailed { status: Some(5), .. }) => { - stop_service_elevated().map_err(|error| RustdeskError::CommandFailed { - command: format!("stop_service_elevated ({error})"), - status: Some(5), - }) - } - Err(error) => Err(error), - } -} - -fn run_sc(args: &[&str]) -> Result<(), RustdeskError> { - let status = hidden_command("sc") - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("sc {}", args.join(" ")), - status: status.code(), - }); - } - Ok(()) -} - -fn ensure_service_installed(exe_path: &Path) -> Result<(), RustdeskError> { - if run_sc(&["query", SERVICE_NAME]).is_ok() { - return Ok(()); - } - - log_event("Serviço RustDesk não encontrado; instalando via CLI"); - run_with_args(exe_path, &["--install-service"])?; - Ok(()) -} - -fn query_id_with_retries(exe_path: &Path, attempts: usize) -> Result { - let mut last_error: Option = None; - for attempt in 0..attempts { - match query_id(exe_path) { - Ok(value) if !value.trim().is_empty() => return Ok(value), - Ok(_) => { - last_error = Some(RustdeskError::MissingId); - } - Err(error) => { - last_error = Some(error); - } - } - if attempt + 1 < attempts { - thread::sleep(Duration::from_millis(800)); - } - } - Err(last_error.unwrap_or(RustdeskError::MissingId)) -} - -fn query_id(exe_path: &Path) -> Result { - let output = hidden_command(exe_path) - .arg("--get-id") - .output()?; - if !output.status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --get-id", exe_path.display()), - status: output.status.code(), - }); - } - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if stdout.is_empty() { - return Err(RustdeskError::MissingId); - } - Ok(stdout) -} - -fn query_version(exe_path: &Path) -> Result { - let output = hidden_command(exe_path) - .arg("--version") - .output()?; - if !output.status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} --version", exe_path.display()), - status: output.status.code(), - }); - } - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) -} - -fn ensure_remote_id_files(id: &str) { - for dir in remote_id_directories() { - let path = dir.join("RustDesk_local.toml"); - match write_remote_id_value(&path, id) { - Ok(_) => log_event(format!( - "remote_id atualizado para {} em {}", - id, - path.display() - )), - Err(error) => log_event(format!( - "Falha ao atualizar remote_id em {}: {error}", - path.display() - )), - } - } -} - -fn remote_id_directories() -> Vec { - let mut dirs = Vec::new(); - dirs.push(program_data_config_dir()); - for profile in service_profile_dirs() { - dirs.push(profile); - } - if let Some(appdir) = user_appdata_config_dir() { - dirs.push(appdir); - } - dirs -} - -fn service_profile_dirs() -> Vec { - vec![ - PathBuf::from(LOCAL_SERVICE_CONFIG), - PathBuf::from(LOCAL_SYSTEM_CONFIG), - ] -} - -fn propagation_destinations() -> Vec { - let mut dirs = Vec::new(); - dirs.push(program_data_config_dir()); - dirs.extend(service_profile_dirs()); - dirs -} - -fn ensure_password_files(secret: &str) -> Result<(), String> { - let mut errors = Vec::new(); - - for dir in remote_id_directories() { - let password_path = dir.join("RustDesk.toml"); - if let Err(error) = write_toml_kv(&password_path, "password", secret) { - errors.push(format!("{} -> {}", password_path.display(), error)); - } else { - log_event(format!( - "Senha escrita via fallback em {}", - password_path.display() - )); - } - - let local_path = dir.join("RustDesk_local.toml"); - if let Err(error) = write_toml_kv(&local_path, "verification-method", SECURITY_VERIFICATION_VALUE) { - log_event(format!( - "Falha ao ajustar verification-method em {}: {error}", - local_path.display() - )); - } else { - log_event(format!( - "verification-method atualizado para {} em {}", - SECURITY_VERIFICATION_VALUE, - local_path.display() - )); - } - - let rustdesk2_path = dir.join("RustDesk2.toml"); - if let Err(error) = enforce_security_in_rustdesk2(&rustdesk2_path) { - log_event(format!( - "Falha ao ajustar flags no RustDesk2.toml em {}: {error}", - rustdesk2_path.display() - )); - } - - if let Err(error) = write_toml_kv(&local_path, "approve-mode", SECURITY_APPROVE_MODE_VALUE) { - log_event(format!( - "Falha ao ajustar approve-mode em {}: {error}", - local_path.display() - )); - } else { - log_event(format!( - "approve-mode atualizado para {} em {}", - SECURITY_APPROVE_MODE_VALUE, - local_path.display() - )); - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors.join(" | ")) - } -} - -fn enforce_security_flags() -> Result<(), String> { - let mut errors = Vec::new(); - for dir in remote_id_directories() { - let local_path = dir.join("RustDesk_local.toml"); - if let Err(error) = write_toml_kv(&local_path, "verification-method", SECURITY_VERIFICATION_VALUE) { - errors.push(format!("{} -> {}", local_path.display(), error)); - } else { - log_event(format!( - "verification-method atualizado para {} em {}", - SECURITY_VERIFICATION_VALUE, - local_path.display() - )); - } - - if let Err(error) = write_toml_kv(&local_path, "approve-mode", SECURITY_APPROVE_MODE_VALUE) { - errors.push(format!("{} -> {}", local_path.display(), error)); - } else { - log_event(format!( - "approve-mode atualizado para {} em {}", - SECURITY_APPROVE_MODE_VALUE, - local_path.display() - )); - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors.join(" | ")) - } -} - -fn enforce_security_in_rustdesk2(path: &Path) -> io::Result<()> { - write_toml_kv(path, "verification-method", SECURITY_VERIFICATION_VALUE)?; - write_toml_kv(path, "approve-mode", SECURITY_APPROVE_MODE_VALUE)?; - Ok(()) -} - -fn propagate_password_profile() -> io::Result { - let Some(src_dir) = user_appdata_config_dir() else { - log_event("AppData do usuário não disponível para copiar RustDesk.toml (propagação ignorada)"); - return Ok(false); - }; - - let mut propagated = false; - - for filename in PROPAGATION_FILES { - let src_path = src_dir.join(filename); - if !src_path.exists() { - continue; - } - log_event(format!( - "Copiando {} para ProgramData/serviços", - src_path.display() - )); - - for dest_root in propagation_destinations() { - let target_path = dest_root.join(filename); - copy_overwrite(&src_path, &target_path)?; - log_event(format!( - "{} propagado para {}", - filename, - target_path.display() - )); - propagated = true; - } - } - - if !propagated { - log_event("Nenhum arquivo de perfil encontrado para propagação; aplicando fallback"); - } - - Ok(propagated) -} - -fn replicate_password_artifacts() -> io::Result<()> { - let Some(src) = user_appdata_config_dir() else { - return Ok(()); - }; - let destinations = propagation_destinations(); - let candidates = ["password", "passwd", "passwd.txt"]; - - for dest in destinations { - fs::create_dir_all(&dest)?; - for name in candidates { - let source_path = src.join(name); - if !source_path.exists() { - continue; - } - let metadata = match fs::metadata(&source_path) { - Ok(data) => data, - Err(_) => continue, - }; - if !metadata.is_file() || metadata.len() == 0 { - continue; - } - - let target_path = dest.join(name); - copy_overwrite(&source_path, &target_path)?; - log_event(format!( - "Artefato de senha {name} replicado para {}", - target_path.display() - )); - } - } - - Ok(()) -} - -fn purge_existing_rustdesk_profiles() -> Result<(), String> { - let mut errors = Vec::new(); - - for dir in remote_id_directories() { - match purge_config_dir(&dir) { - Ok(true) => { - log_event(format!( - "Perfis antigos removidos em {}", - dir.display() - )); - } - Ok(false) => {} - Err(error) => errors.push(format!("{} -> {error}", dir.display())), - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors.join(" | ")) - } -} - -fn purge_config_dir(dir: &Path) -> Result { - if !dir.exists() { - return Ok(false); - } - - let mut removed = false; - fs::create_dir_all(dir)?; - - for name in RUSTDESK_CONFIG_FILES { - let path = dir.join(name); - if path.is_dir() { - fs::remove_dir_all(&path)?; - removed = true; - continue; - } - if path.exists() { - fs::remove_file(&path)?; - removed = true; - } - } - - Ok(removed) -} - -#[allow(dead_code)] -fn run_powershell_elevated(script: &str) -> Result<(), String> { - let temp_dir = env::temp_dir(); - let payload = temp_dir.join("raven_payload.ps1"); - fs::write(&payload, script).map_err(|error| format!("write payload: {error}"))?; - - let launcher = temp_dir.join("raven_launcher.ps1"); - let launcher_body = format!( - r#" -$ErrorActionPreference='Stop' -$psi = New-Object System.Diagnostics.ProcessStartInfo -$psi.FileName = 'powershell.exe' -$psi.Arguments = '-NoProfile -ExecutionPolicy Bypass -File "{payload}"' -$psi.Verb = 'runas' -$psi.WindowStyle = 'Hidden' -$process = [System.Diagnostics.Process]::Start($psi) -$process.WaitForExit() -exit $process.ExitCode -"#, - payload = payload.display() - ); - fs::write(&launcher, launcher_body).map_err(|error| format!("write launcher: {error}"))?; - - let status = Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args([ - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-File", - &launcher.to_string_lossy(), - ]) - .status() - .map_err(|error| format!("spawn ps: {error}"))?; - - let _ = fs::remove_file(&launcher); - let _ = fs::remove_file(&payload); - - if let Some(code) = status.code() { - if code == 0 || code == 1 { - return Ok(()); - } - } else if status.success() { - return Ok(()); - } - Err(format!("elevated ps exit {:?}", status.code())) -} - -#[allow(dead_code)] -fn fix_profile_acl(target: &Path) -> Result<(), String> { - let target_str = target.display().to_string(); - let transcript = env::temp_dir().join("raven_acl_ps.log"); - let log_str = transcript.display().to_string(); - let script = format!( - r#" -$ErrorActionPreference='Stop' -Start-Transcript -Path '{log}' -Force -try {{ - if (-not (Test-Path '{target}')) {{ New-Item -ItemType Directory -Force -Path '{target}' | Out-Null }} - - & takeown /F '{target}' /R /D Y - $takeCode = $LASTEXITCODE - - & icacls '{target}' /grant '*S-1-5-32-544:(OI)(CI)F' '*S-1-5-19:(OI)(CI)F' '*S-1-5-32-545:(OI)(CI)M' /T /C /Q - $icaCode = $LASTEXITCODE - - if (($takeCode -eq 0) -and ($icaCode -in 0,1)) {{ exit 0 }} - if ($icaCode -ne 0) {{ exit $icaCode }} - exit $takeCode -}} catch {{ - Write-Host ("exception: " + ($_.Exception.Message)) - exit 1 -}} finally {{ - try {{ Stop-Transcript | Out-Null }} catch {{ }} -}} -"#, - target = target_str, - log = log_str - ); - - let result = run_powershell_elevated(&script); - if result.is_err() { - if let Ok(content) = fs::read_to_string(&transcript) { - log_event(format!( - "ACL transcript para {}:\n{}", - target.display(), content - )); - } - } - let _ = fs::remove_file(&transcript); - result -} - -fn ensure_service_profiles_writable_preflight() -> Result<(), String> { - // Verificamos se os diretorios de perfil sao graváveis - // Se nao forem, apenas logamos aviso - o Raven Service deve lidar com isso - // Nao usamos elevacao para evitar UAC adicional - let mut blocked_dirs = Vec::new(); - for dir in service_profile_dirs() { - if !can_write_dir(&dir) { - blocked_dirs.push(dir); - } - } - - if blocked_dirs.is_empty() { - return Ok(()); - } - - // Apenas logamos aviso - o serviço RavenService deve lidar com permissões - log_event(format!( - "Aviso: alguns perfis de serviço não são graváveis: {:?}. O Raven Service deve configurar permissões.", - blocked_dirs.iter().map(|d| d.display().to_string()).collect::>() - )); - - // Retornamos Ok para não bloquear o fluxo - // O Raven Service, rodando como LocalSystem, pode gravar nesses diretórios - Ok(()) -} - -fn stop_service_elevated() -> Result<(), String> { - // Tentamos parar o serviço RustDesk sem elevação - // Se falhar, apenas logamos aviso - o Raven Service pode lidar com isso - // Não usamos elevação para evitar UAC adicional - let output = Command::new("sc") - .args(["stop", "RustDesk"]) - .output(); - - match output { - Ok(result) => { - if result.status.success() { - // Aguarda um pouco para o serviço parar - std::thread::sleep(std::time::Duration::from_secs(2)); - Ok(()) - } else { - let stderr = String::from_utf8_lossy(&result.stderr); - log_event(format!( - "Aviso: não foi possível parar o serviço RustDesk sem elevação: {}", - stderr.trim() - )); - // Retornamos Ok para não bloquear - o serviço pode estar já parado - Ok(()) - } - } - Err(e) => { - log_event(format!("Aviso: falha ao executar sc stop RustDesk: {e}")); - Ok(()) - } - } -} - -fn can_write_dir(dir: &Path) -> bool { - if fs::create_dir_all(dir).is_err() { - return false; - } - let probe = dir.join(".raven_acl_probe"); - match OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&probe) - { - Ok(mut file) => { - if file.write_all(b"ok").is_err() { - let _ = fs::remove_file(&probe); - return false; - } - let _ = fs::remove_file(&probe); - true - } - Err(_) => false, - } -} - -fn write_remote_id_value(path: &Path, id: &str) -> io::Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let replacement = format!("remote_id = '{}'\n", id); - if let Ok(existing) = fs::read_to_string(path) { - let mut replaced = false; - let mut buffer = String::with_capacity(existing.len() + replacement.len()); - for line in existing.lines() { - if line.trim_start().starts_with("remote_id") { - buffer.push_str(&replacement); - replaced = true; - } else { - buffer.push_str(line); - buffer.push('\n'); - } - } - if !replaced { - buffer.push_str(&replacement); - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(buffer.as_bytes()) - } else { - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(replacement.as_bytes()) - } -} - -fn write_toml_kv(path: &Path, key: &str, value: &str) -> io::Result<()> { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - if path.is_dir() { - fs::remove_dir_all(path)?; - } - let sanitized = value.replace('\\', "\\\\").replace('"', "\\\""); - let replacement = format!("{key} = \"{sanitized}\"\n"); - let existing = fs::read_to_string(path).unwrap_or_default(); - let mut replaced = false; - let mut buffer = String::with_capacity(existing.len() + replacement.len()); - for line in existing.lines() { - let trimmed = line.trim_start(); - if trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}=")) { - buffer.push_str(&replacement); - replaced = true; - } else { - buffer.push_str(line); - buffer.push('\n'); - } - } - if !replaced { - buffer.push_str(&replacement); - } - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - file.write_all(buffer.as_bytes()) -} - -fn read_remote_id_from_profiles() -> Option { - for dir in remote_id_directories() { - for candidate in [dir.join("RustDesk_local.toml"), dir.join("RustDesk.toml")] { - if let Some(id) = read_remote_id_file(&candidate) { - if !id.is_empty() { - return Some(id); - } - } - } - } - None -} - -fn read_remote_id_file(path: &Path) -> Option { - let content = fs::read_to_string(path).ok()?; - for line in content.lines() { - if let Some(value) = parse_assignment(line, "remote_id") { - return Some(value); - } - } - None -} - -fn parse_assignment(line: &str, key: &str) -> Option { - let trimmed = line.trim(); - if !trimmed.starts_with(key) { - return None; - } - let (_, rhs) = trimmed.split_once('=')?; - let value = rhs.trim().trim_matches(|c| c == '\'' || c == '"'); - if value.is_empty() { - None - } else { - Some(value.to_string()) - } -} - -fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> { - let status = hidden_command(exe_path) - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - return Err(RustdeskError::CommandFailed { - command: format!("{} {}", exe_path.display(), args.join(" ")), - status: status.code(), - }); - } - Ok(()) -} - -fn log_password_replication(secret: &str) { - for dir in remote_id_directories() { - let primary = dir.join("RustDesk.toml"); - log_password_match(&primary, secret); - - let local_path = dir.join("RustDesk_local.toml"); - log_password_match(&local_path, secret); - } -} - -fn log_password_match(path: &Path, secret: &str) { - match read_password_from_file(path) { - Some(value) if value == secret => { - log_event(format!( - "Senha confirmada em {} ({})", - path.display(), - mask_secret(&value) - )); - } - Some(value) => { - log_event(format!( - "Aviso: senha divergente ({}) em {}", - mask_secret(&value), - path.display() - )); - } - None => { - log_event(format!( - "Aviso: chave 'password' não encontrada em {}", - path.display() - )); - } - } -} - -fn read_password_from_file(path: &Path) -> Option { - let content = fs::read_to_string(path).ok()?; - for line in content.lines() { - if let Some(value) = parse_assignment(line, "password") { - return Some(value); - } - } - None -} - -fn mask_secret(secret: &str) -> String { - if secret.is_empty() { - return "".to_string(); - } - let chars: Vec = secret.chars().collect(); - if chars.len() <= 4 { - return "*".repeat(chars.len()); - } - let prefix: String = chars.iter().take(2).copied().collect(); - let suffix: String = chars - .iter() - .rev() - .take(2) - .copied() - .collect::>() - .into_iter() - .rev() - .collect(); - format!("{}***{}", prefix, suffix) -} - -fn hidden_command(program: impl AsRef) -> Command { - let mut cmd = Command::new(program); - cmd.creation_flags(CREATE_NO_WINDOW); - cmd -} - -fn copy_overwrite(src: &Path, dst: &Path) -> io::Result<()> { - if let Some(parent) = dst.parent() { - fs::create_dir_all(parent)?; - } - if dst.is_dir() { - fs::remove_dir_all(dst)?; - } else if dst.exists() { - fs::remove_file(dst)?; - } - fs::copy(src, dst)?; - Ok(()) -} - -fn log_event(message: impl AsRef) { - if let Some(dir) = logs_directory() { - if let Err(error) = append_log(dir, message.as_ref()) { - eprintln!("[rustdesk][log] Falha ao registrar log: {error}"); - } - } -} - -fn logs_directory() -> Option { - let base = env::var("LOCALAPPDATA").ok()?; - Some( - Path::new(&base) - .join("br.com.esdrasrenan.sistemadechamados") - .join("logs"), - ) -} - -fn append_log(dir: PathBuf, message: &str) -> io::Result<()> { - fs::create_dir_all(&dir)?; - let log_path = dir.join("rustdesk.log"); - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(log_path)?; - let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S"); - writeln!(file, "[{timestamp}] {message}")?; - Ok(()) -} - -fn raven_appdata_root() -> Option { - env::var("LOCALAPPDATA") - .ok() - .map(|value| Path::new(&value).join(APP_IDENTIFIER)) -} - -fn machine_store_path() -> Option { - raven_appdata_root().map(|dir| dir.join(MACHINE_STORE_FILENAME)) -} - -fn read_machine_store_object() -> Option> { - let path = machine_store_path()?; - let contents = fs::read_to_string(path).ok()?; - let value: JsonValue = serde_json::from_str(&contents).ok()?; - value.as_object().cloned() -} - -fn write_machine_store_object(map: JsonMap) -> Result<(), String> { - let path = machine_store_path().ok_or_else(|| "LOCALAPPDATA não disponível".to_string())?; - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).map_err(|error| format!("mkdir AppData: {error}"))?; - } - let serialized = serde_json::to_vec_pretty(&JsonValue::Object(map)) - .map_err(|error| format!("serialize machine-agent: {error}"))?; - fs::write(&path, serialized).map_err(|error| format!("write machine-agent: {error}"))?; - Ok(()) -} - -fn upsert_machine_store_value(key: &str, value: JsonValue) -> Result<(), String> { - let mut map = read_machine_store_object().unwrap_or_default(); - map.insert(key.to_string(), value); - write_machine_store_object(map) -} - -#[allow(dead_code)] -fn machine_store_key_exists(key: &str) -> bool { - read_machine_store_object() - .map(|map| map.contains_key(key)) - .unwrap_or(false) -} - -#[allow(dead_code)] -fn acl_flag_file_path() -> Option { - raven_appdata_root().map(|dir| dir.join(ACL_FLAG_FILENAME)) -} - -#[allow(dead_code)] -fn has_acl_unlock_flag() -> bool { - if let Some(flag) = acl_flag_file_path() { - if flag.exists() { - return true; - } - } - machine_store_key_exists(RUSTDESK_ACL_STORE_KEY) -} - -#[allow(dead_code)] -fn mark_acl_unlock_flag() { - let timestamp = Utc::now().timestamp_millis(); - if let Some(flag_path) = acl_flag_file_path() { - if let Some(parent) = flag_path.parent() { - let _ = fs::create_dir_all(parent); - } - if let Err(error) = fs::write(&flag_path, timestamp.to_string()) { - log_event(format!( - "Falha ao gravar flag de ACL em {}: {error}", - flag_path.display() - )); - } - } - - if let Err(error) = upsert_machine_store_value(RUSTDESK_ACL_STORE_KEY, JsonValue::from(timestamp)) { - log_event(format!( - "Falha ao registrar flag de ACL no machine-agent: {error}" - )); - } -} - -fn get_machine_store_path() -> Result { - let base = env::var("LOCALAPPDATA") - .map_err(|_| RustdeskError::MissingId)?; - Ok(Path::new(&base) - .join(APP_IDENTIFIER) - .join(MACHINE_STORE_FILENAME)) -} - -fn sync_remote_access_with_backend(result: &crate::RustdeskProvisioningResult) -> Result<(), RustdeskError> { - log_event("Iniciando sincronizacao com backend..."); - - // Le token e config do store - let store_path = get_machine_store_path()?; - let store_content = fs::read_to_string(&store_path) - .map_err(RustdeskError::Io)?; - let store: serde_json::Value = serde_json::from_str(&store_content) - .map_err(|_| RustdeskError::MissingId)?; - - let token = store.get("token") - .and_then(|v| v.as_str()) - .ok_or(RustdeskError::MissingId)?; - - let config = store.get("config") - .ok_or(RustdeskError::MissingId)?; - - let machine_id = config.get("machineId") - .and_then(|v| v.as_str()) - .ok_or(RustdeskError::MissingId)?; - - let api_base_url = config.get("apiBaseUrl") - .and_then(|v| v.as_str()) - .unwrap_or("https://tickets.esdrasrenan.com.br"); - - log_event(format!("Sincronizando com backend: {} (machineId: {})", api_base_url, machine_id)); - - // Monta payload conforme schema esperado pelo backend - // Schema: { machineToken, provider, identifier, password?, url?, username?, notes? } - let payload = serde_json::json!({ - "machineToken": token, - "provider": "RustDesk", - "identifier": result.id, - "password": result.password, - "notes": format!("Versao: {}. Provisionado em: {}", - result.installed_version.as_deref().unwrap_or("desconhecida"), - result.last_provisioned_at) - }); - - // Faz POST para /api/machines/remote-access - let client = Client::builder() - .user_agent(USER_AGENT) - .timeout(Duration::from_secs(30)) - .build()?; - - let url = format!("{}/api/machines/remote-access", api_base_url); - let response = client.post(&url) - .header("Content-Type", "application/json") - .header("Idempotency-Key", format!("{}:RustDesk:{}", machine_id, result.id)) - .body(payload.to_string()) - .send()?; - - if response.status().is_success() { - log_event(format!("Sync com backend OK: status {}", response.status())); - Ok(()) - } else { - let status = response.status(); - let body = response.text().unwrap_or_default(); - let body_preview = if body.len() > 200 { &body[..200] } else { &body }; - log_event(format!("Sync com backend falhou: {} - {}", status, body_preview)); - Err(RustdeskError::CommandFailed { - command: "sync_remote_access".to_string(), - status: Some(status.as_u16() as i32) - }) - } -} diff --git a/apps/desktop/src-tauri/src/service_client.rs b/apps/desktop/src-tauri/src/service_client.rs deleted file mode 100644 index f2af2ed..0000000 --- a/apps/desktop/src-tauri/src/service_client.rs +++ /dev/null @@ -1,244 +0,0 @@ -//! Cliente IPC para comunicacao com o Raven Service -//! -//! Este modulo permite que o app Tauri se comunique com o Raven Service -//! via Named Pipes para executar operacoes privilegiadas. - -#![allow(dead_code)] - -use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader, Write}; -use std::time::Duration; -use thiserror::Error; - -const PIPE_NAME: &str = r"\\.\pipe\RavenService"; - -#[derive(Debug, Error)] -pub enum ServiceClientError { - #[error("Servico nao disponivel: {0}")] - ServiceUnavailable(String), - - #[error("Erro de comunicacao: {0}")] - CommunicationError(String), - - #[error("Erro de serializacao: {0}")] - SerializationError(#[from] serde_json::Error), - - #[error("Erro do servico: {message} (code: {code})")] - ServiceError { code: i32, message: String }, - - #[error("Timeout aguardando resposta")] - Timeout, -} - -#[derive(Debug, Serialize)] -struct Request { - id: String, - method: String, - params: serde_json::Value, -} - -#[derive(Debug, Deserialize)] -struct Response { - id: String, - result: Option, - error: Option, -} - -#[derive(Debug, Deserialize)] -struct ErrorResponse { - code: i32, - message: String, -} - -// ============================================================================= -// Tipos de Resultado -// ============================================================================= - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UsbPolicyResult { - pub success: bool, - pub policy: String, - pub error: Option, - pub applied_at: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RustdeskResult { - pub id: String, - pub password: String, - pub installed_version: Option, - pub updated: bool, - pub last_provisioned_at: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RustdeskStatus { - pub installed: bool, - pub running: bool, - pub id: Option, - pub version: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct HealthCheckResult { - pub status: String, - pub service: String, - pub version: String, - pub timestamp: i64, -} - -// ============================================================================= -// Cliente -// ============================================================================= - -/// Verifica se o servico esta disponivel -pub fn is_service_available() -> bool { - health_check().is_ok() -} - -/// Verifica saude do servico -pub fn health_check() -> Result { - let response = call_service("health_check", serde_json::json!({}))?; - serde_json::from_value(response).map_err(|e| e.into()) -} - -/// Aplica politica de USB -pub fn apply_usb_policy(policy: &str) -> Result { - let response = call_service( - "apply_usb_policy", - serde_json::json!({ "policy": policy }), - )?; - serde_json::from_value(response).map_err(|e| e.into()) -} - -/// Obtem politica de USB atual -pub fn get_usb_policy() -> Result { - let response = call_service("get_usb_policy", serde_json::json!({}))?; - response - .get("policy") - .and_then(|p| p.as_str()) - .map(String::from) - .ok_or_else(|| ServiceClientError::CommunicationError("Resposta invalida".into())) -} - -/// Provisiona RustDesk -pub fn provision_rustdesk( - config: Option<&str>, - password: Option<&str>, - machine_id: Option<&str>, -) -> Result { - let params = serde_json::json!({ - "config": config, - "password": password, - "machineId": machine_id, - }); - let response = call_service("provision_rustdesk", params)?; - serde_json::from_value(response).map_err(|e| e.into()) -} - -/// Obtem status do RustDesk -pub fn get_rustdesk_status() -> Result { - let response = call_service("get_rustdesk_status", serde_json::json!({}))?; - serde_json::from_value(response).map_err(|e| e.into()) -} - -// ============================================================================= -// Comunicacao IPC -// ============================================================================= - -fn call_service( - method: &str, - params: serde_json::Value, -) -> Result { - // Gera ID unico para a requisicao - let id = uuid::Uuid::new_v4().to_string(); - - let request = Request { - id: id.clone(), - method: method.to_string(), - params, - }; - - // Serializa requisicao - let request_json = serde_json::to_string(&request)?; - - // Conecta ao pipe - let mut pipe = connect_to_pipe()?; - - // Envia requisicao - writeln!(pipe, "{}", request_json).map_err(|e| { - ServiceClientError::CommunicationError(format!("Erro ao enviar requisicao: {}", e)) - })?; - pipe.flush().map_err(|e| { - ServiceClientError::CommunicationError(format!("Erro ao flush: {}", e)) - })?; - - // Le resposta - let mut reader = BufReader::new(pipe); - let mut response_line = String::new(); - - reader.read_line(&mut response_line).map_err(|e| { - ServiceClientError::CommunicationError(format!("Erro ao ler resposta: {}", e)) - })?; - - // Parse da resposta - let response: Response = serde_json::from_str(&response_line)?; - - // Verifica se o ID bate - if response.id != id { - return Err(ServiceClientError::CommunicationError( - "ID de resposta nao corresponde".into(), - )); - } - - // Verifica erro - if let Some(error) = response.error { - return Err(ServiceClientError::ServiceError { - code: error.code, - message: error.message, - }); - } - - // Retorna resultado - response - .result - .ok_or_else(|| ServiceClientError::CommunicationError("Resposta sem resultado".into())) -} - -#[cfg(target_os = "windows")] -fn connect_to_pipe() -> Result { - // Tenta conectar ao pipe com retry - let mut attempts = 0; - let max_attempts = 3; - - loop { - match std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(PIPE_NAME) - { - Ok(file) => return Ok(file), - Err(e) => { - attempts += 1; - if attempts >= max_attempts { - return Err(ServiceClientError::ServiceUnavailable(format!( - "Nao foi possivel conectar ao servico apos {} tentativas: {}", - max_attempts, e - ))); - } - std::thread::sleep(Duration::from_millis(500)); - } - } - } -} - -#[cfg(not(target_os = "windows"))] -fn connect_to_pipe() -> Result { - Err(ServiceClientError::ServiceUnavailable( - "Named Pipes so estao disponiveis no Windows".into(), - )) -} diff --git a/apps/desktop/src-tauri/src/usb_control.rs b/apps/desktop/src-tauri/src/usb_control.rs deleted file mode 100644 index a95e0a5..0000000 --- a/apps/desktop/src-tauri/src/usb_control.rs +++ /dev/null @@ -1,408 +0,0 @@ -//! USB Storage Control Module -//! -//! Este modulo implementa o controle de dispositivos de armazenamento USB no Windows. -//! Utiliza duas abordagens complementares: -//! 1. Removable Storage Access Policy (via registro do Windows) -//! 2. USBSTOR driver control (como fallback/reforco) -//! -//! IMPORTANTE: Requer privilegios de administrador para funcionar. - -use serde::{Deserialize, Serialize}; -use std::io; -use thiserror::Error; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum UsbPolicy { - Allow, - BlockAll, - Readonly, -} - -impl UsbPolicy { - pub fn from_str(s: &str) -> Option { - match s.to_uppercase().as_str() { - "ALLOW" => Some(Self::Allow), - "BLOCK_ALL" => Some(Self::BlockAll), - "READONLY" => Some(Self::Readonly), - _ => None, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - Self::Allow => "ALLOW", - Self::BlockAll => "BLOCK_ALL", - Self::Readonly => "READONLY", - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UsbPolicyResult { - pub success: bool, - pub policy: String, - pub error: Option, - pub applied_at: Option, -} - -#[derive(Error, Debug)] -#[allow(dead_code)] -pub enum UsbControlError { - #[error("Politica USB invalida: {0}")] - InvalidPolicy(String), - #[error("Erro de registro do Windows: {0}")] - RegistryError(String), - #[error("Permissao negada - requer privilegios de administrador")] - PermissionDenied, - #[error("Sistema operacional nao suportado")] - UnsupportedOs, - #[error("Erro de I/O: {0}")] - Io(#[from] io::Error), -} - -#[cfg(target_os = "windows")] -mod windows_impl { - use super::*; - use std::fs; - use std::path::PathBuf; - use std::process::Command; - use winreg::enums::*; - use winreg::RegKey; - - // GUID para Removable Storage Devices (Disk) - const REMOVABLE_STORAGE_GUID: &str = "{53f56307-b6bf-11d0-94f2-00a0c91efb8b}"; - - // Chaves de registro - const REMOVABLE_STORAGE_PATH: &str = - r"Software\Policies\Microsoft\Windows\RemovableStorageDevices"; - const USBSTOR_PATH: &str = r"SYSTEM\CurrentControlSet\Services\USBSTOR"; - const STORAGE_POLICY_PATH: &str = r"SYSTEM\CurrentControlSet\Control\StorageDevicePolicies"; - - pub fn apply_usb_policy(policy: UsbPolicy) -> Result { - let now = chrono::Utc::now().timestamp_millis(); - - let direct_result = try_apply_policy_direct(policy); - - match direct_result { - Ok(()) => Ok(UsbPolicyResult { - success: true, - policy: policy.as_str().to_string(), - error: None, - applied_at: Some(now), - }), - Err(err) => { - // Se faltou permissão, retorna erro - o serviço deve ser usado - // Não fazemos elevação aqui para evitar UAC adicional - if is_permission_error(&err) { - return Err(UsbControlError::PermissionDenied); - } - Err(err) - } - } - } - - fn try_apply_policy_direct(policy: UsbPolicy) -> Result<(), UsbControlError> { - // 1. Aplicar Removable Storage Access Policy - apply_removable_storage_policy(policy)?; - - // 2. Aplicar USBSTOR como reforco - apply_usbstor_policy(policy)?; - - // 3. Aplicar WriteProtect se necessario - if policy == UsbPolicy::Readonly { - apply_write_protect(true)?; - } else { - apply_write_protect(false)?; - } - - Ok(()) - } - - fn apply_removable_storage_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); - - match policy { - UsbPolicy::Allow => { - // Tenta remover as restricoes, se existirem - if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_ALL_ACCESS) { - let _ = key.delete_value("Deny_Read"); - let _ = key.delete_value("Deny_Write"); - let _ = key.delete_value("Deny_Execute"); - } - // Tenta remover a chave inteira se estiver vazia - let _ = hklm.delete_subkey(&full_path); - } - UsbPolicy::BlockAll => { - let (key, _) = hklm - .create_subkey(&full_path) - .map_err(map_winreg_error)?; - - key.set_value("Deny_Read", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Write", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Execute", &1u32) - .map_err(map_winreg_error)?; - } - UsbPolicy::Readonly => { - let (key, _) = hklm - .create_subkey(&full_path) - .map_err(map_winreg_error)?; - - // Permite leitura, bloqueia escrita - key.set_value("Deny_Read", &0u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Write", &1u32) - .map_err(map_winreg_error)?; - key.set_value("Deny_Execute", &0u32) - .map_err(map_winreg_error)?; - } - } - - Ok(()) - } - - fn apply_usbstor_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - let key = hklm - .open_subkey_with_flags(USBSTOR_PATH, KEY_ALL_ACCESS) - .map_err(map_winreg_error)?; - - match policy { - UsbPolicy::Allow => { - // Start = 3 habilita o driver - key.set_value("Start", &3u32) - .map_err(map_winreg_error)?; - } - UsbPolicy::BlockAll | UsbPolicy::Readonly => { - // Start = 4 desabilita o driver - // Nota: Para Readonly, mantemos o driver ativo mas com WriteProtect - // Porem, como fallback de seguranca, desabilitamos para BlockAll - if policy == UsbPolicy::BlockAll { - key.set_value("Start", &4u32) - .map_err(map_winreg_error)?; - } else { - // Readonly mantem driver ativo - key.set_value("Start", &3u32) - .map_err(map_winreg_error)?; - } - } - } - - Ok(()) - } - - fn apply_write_protect(enable: bool) -> Result<(), UsbControlError> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - if enable { - let (key, _) = hklm - .create_subkey(STORAGE_POLICY_PATH) - .map_err(map_winreg_error)?; - - key.set_value("WriteProtect", &1u32) - .map_err(map_winreg_error)?; - } else if let Ok(key) = hklm.open_subkey_with_flags(STORAGE_POLICY_PATH, KEY_ALL_ACCESS) { - let _ = key.set_value("WriteProtect", &0u32); - } - - Ok(()) - } - - pub fn get_current_policy() -> Result { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - // Verifica Removable Storage Policy primeiro - let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); - - if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_READ) { - let deny_read: u32 = key.get_value("Deny_Read").unwrap_or(0); - let deny_write: u32 = key.get_value("Deny_Write").unwrap_or(0); - - if deny_read == 1 && deny_write == 1 { - return Ok(UsbPolicy::BlockAll); - } - - if deny_read == 0 && deny_write == 1 { - return Ok(UsbPolicy::Readonly); - } - } - - // Verifica USBSTOR como fallback - if let Ok(key) = hklm.open_subkey_with_flags(USBSTOR_PATH, KEY_READ) { - let start: u32 = key.get_value("Start").unwrap_or(3); - if start == 4 { - return Ok(UsbPolicy::BlockAll); - } - } - - Ok(UsbPolicy::Allow) - } - - fn is_permission_error(error: &UsbControlError) -> bool { - match error { - UsbControlError::PermissionDenied => true, - UsbControlError::RegistryError(msg) => { - let lower = msg.to_lowercase(); - lower.contains("access is denied") || lower.contains("acesso negado") || lower.contains("5") - } - _ => false, - } - } - - #[allow(dead_code)] - fn apply_policy_with_elevation(policy: UsbPolicy) -> Result<(), UsbControlError> { - // Cria script temporário para aplicar as chaves via PowerShell elevado - let temp_dir = std::env::temp_dir(); - let script_path: PathBuf = temp_dir.join("raven_usb_policy.ps1"); - - let policy_str = policy.as_str(); - let script = format!( - r#"$ErrorActionPreference = 'Stop' -$guid = '{guid}' -$policy = '{policy}' - -function Set-Allow {{ - reg delete 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f 2>$null - reg delete 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f 2>$null - reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 3 /f | Out-Null -}} - -function Set-BlockAll {{ - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Read /t REG_DWORD /d 1 /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Write /t REG_DWORD /d 1 /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Execute /t REG_DWORD /d 1 /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 4 /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /v WriteProtect /t REG_DWORD /d 0 /f | Out-Null -}} - -function Set-Readonly {{ - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Read /t REG_DWORD /d 0 /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Write /t REG_DWORD /d 1 /f | Out-Null - reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Execute /t REG_DWORD /d 0 /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 3 /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f | Out-Null - reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /v WriteProtect /t REG_DWORD /d 1 /f | Out-Null -}} - -switch ($policy) {{ - 'ALLOW' {{ Set-Allow }} - 'BLOCK_ALL' {{ Set-BlockAll }} - 'READONLY' {{ Set-Readonly }} - default {{ throw 'Politica invalida' }} -}} - -try {{ - gpupdate /target:computer /force | Out-Null -}} catch {{}} -"#, - guid = REMOVABLE_STORAGE_GUID, - policy = policy_str - ); - - fs::write(&script_path, script).map_err(UsbControlError::Io)?; - - // Start-Process com RunAs para acionar UAC - let arg = format!( - "Start-Process -WindowStyle Hidden -FilePath powershell -Verb RunAs -Wait -ArgumentList '-ExecutionPolicy Bypass -File \"{}\"'", - script_path.display() - ); - - let status = Command::new("powershell") - .arg("-Command") - .arg(arg) - .status() - .map_err(UsbControlError::Io)?; - - if !status.success() { - return Err(UsbControlError::PermissionDenied); - } - - Ok(()) - } - - fn map_winreg_error(error: io::Error) -> UsbControlError { - if let Some(code) = error.raw_os_error() { - if code == 5 { - return UsbControlError::PermissionDenied; - } - } - UsbControlError::RegistryError(error.to_string()) - } - - pub fn refresh_group_policy() -> Result<(), UsbControlError> { - use std::os::windows::process::CommandExt; - use std::process::Command; - - const CREATE_NO_WINDOW: u32 = 0x08000000; - - // Executa gpupdate para forcar atualizacao das politicas - let output = Command::new("gpupdate") - .args(["/target:computer", "/force"]) - .creation_flags(CREATE_NO_WINDOW) - .output() - .map_err(UsbControlError::Io)?; - - if !output.status.success() { - // Nao e critico se falhar, apenas log - eprintln!( - "[usb_control] gpupdate retornou erro: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - Ok(()) - } -} - -#[cfg(not(target_os = "windows"))] -mod fallback_impl { - use super::*; - - pub fn apply_usb_policy(_policy: UsbPolicy) -> Result { - Err(UsbControlError::UnsupportedOs) - } - - pub fn get_current_policy() -> Result { - Err(UsbControlError::UnsupportedOs) - } - - pub fn refresh_group_policy() -> Result<(), UsbControlError> { - Err(UsbControlError::UnsupportedOs) - } -} - -#[cfg(target_os = "windows")] -pub use windows_impl::*; - -#[cfg(not(target_os = "windows"))] -pub use fallback_impl::*; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_policy_from_str() { - assert_eq!(UsbPolicy::from_str("ALLOW"), Some(UsbPolicy::Allow)); - assert_eq!(UsbPolicy::from_str("BLOCK_ALL"), Some(UsbPolicy::BlockAll)); - assert_eq!(UsbPolicy::from_str("READONLY"), Some(UsbPolicy::Readonly)); - assert_eq!(UsbPolicy::from_str("allow"), Some(UsbPolicy::Allow)); - assert_eq!(UsbPolicy::from_str("invalid"), None); - } - - #[test] - fn test_policy_as_str() { - assert_eq!(UsbPolicy::Allow.as_str(), "ALLOW"); - assert_eq!(UsbPolicy::BlockAll.as_str(), "BLOCK_ALL"); - assert_eq!(UsbPolicy::Readonly.as_str(), "READONLY"); - } -} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json deleted file mode 100644 index b9a94d1..0000000 --- a/apps/desktop/src-tauri/tauri.conf.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "Raven", - "version": "0.2.0", - "identifier": "br.com.esdrasrenan.sistemadechamados", - "build": { - "beforeDevCommand": "bun run dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "bun run build", - "frontendDist": "../dist" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "title": "Raven", - "width": 1100, - "height": 720, - "resizable": true, - "fullscreen": false, - "maximized": true - } - ], - "security": { - "csp": null - } - }, - "plugins": { - "updater": { - "endpoints": [ - "https://raw.githubusercontent.com/esdrasrenan/sistema-de-chamados/main/apps/desktop/public/latest.json" - ], - "dialog": true, - "active": true, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDZDRTBFNkY1NUQ3QzU0QkEKUldTNlZIeGQ5ZWJnYk5mY0J4aWRlb0dRdVZ4TGpBSUZXMnRVUFhmdmlLT0tlY084UjJQUHFWWUkK" - }, - "deep-link": { - "desktop": { - "schemes": ["raven"] - } - } - }, - "bundle": { - "active": true, - "createUpdaterArtifacts": true, - "targets": ["nsis", "deb", "rpm"], - "icon": [ - "icons/icon.ico", - "icons/icon.icns", - "icons/icon.png", - "icons/Raven.png" - ], - "resources": { - "../service/target/release/raven-service.exe": "raven-service.exe" - }, - "windows": { - "webviewInstallMode": { - "type": "skip" - }, - "nsis": { - "displayLanguageSelector": true, - "installerIcon": "icons/icon.ico", - "headerImage": "icons/nsis-header.bmp", - "sidebarImage": "icons/nsis-sidebar.bmp", - "installMode": "perMachine", - "installerHooks": "installer-hooks.nsh", - "languages": ["PortugueseBR"] - } - } - } -} diff --git a/apps/desktop/src/assets/tauri.svg b/apps/desktop/src/assets/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/apps/desktop/src/assets/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/apps/desktop/src/assets/typescript.svg b/apps/desktop/src/assets/typescript.svg deleted file mode 100644 index 30a5edd..0000000 --- a/apps/desktop/src/assets/typescript.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - diff --git a/apps/desktop/src/assets/vite.svg b/apps/desktop/src/assets/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/apps/desktop/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/desktop/src/chat/ChatHubWidget.tsx b/apps/desktop/src/chat/ChatHubWidget.tsx deleted file mode 100644 index 04358d9..0000000 --- a/apps/desktop/src/chat/ChatHubWidget.tsx +++ /dev/null @@ -1,256 +0,0 @@ -/** - * ChatHubWidget - Lista de sessoes de chat ativas usando Convex subscriptions - * - * Arquitetura: - * - Usa useQuery do Convex React para subscription reativa (tempo real verdadeiro) - * - Sem polling - todas as atualizacoes sao push-based via WebSocket - * - Tauri usado apenas para gerenciamento de janelas - */ - -import { useEffect, useState } from "react" -import { invoke } from "@tauri-apps/api/core" -import { Loader2, MessageCircle, ChevronUp, X, Minimize2 } from "lucide-react" -import { useMachineSessions, type MachineSession } from "./useConvexMachineQueries" - -/** - * Hub Widget - Lista todas as sessoes de chat ativas - * Ao clicar em uma sessao, abre/foca a janela de chat daquele ticket - */ -export function ChatHubWidget() { - // Inicializa baseado na altura real da janela (< 100px = minimizado) - const [isMinimized, setIsMinimized] = useState(() => window.innerHeight < 100) - - // Convex subscription reativa - const { sessions = [], isLoading, hasToken } = useMachineSessions() - - // Sincronizar estado minimizado com tamanho da janela - useEffect(() => { - const mountTime = Date.now() - const STABILIZATION_DELAY = 500 - - const handler = () => { - if (Date.now() - mountTime < STABILIZATION_DELAY) { - return - } - const h = window.innerHeight - setIsMinimized(h < 100) - } - window.addEventListener("resize", handler) - return () => window.removeEventListener("resize", handler) - }, []) - - const handleSelectSession = async (ticketId: string, ticketRef: number) => { - try { - // Tauri 2.x auto-converts snake_case (Rust) to camelCase (JS) - await invoke("open_chat_window", { ticketId, ticketRef }) - await invoke("close_hub_window") - } catch (err) { - console.error("open_chat_window FAILED:", err) - } - } - - const handleMinimize = async () => { - setIsMinimized(true) - try { - await invoke("set_hub_minimized", { minimized: true }) - } catch (err) { - console.error("Erro ao minimizar hub:", err) - } - } - - const handleExpand = async () => { - try { - await invoke("set_hub_minimized", { minimized: false }) - setTimeout(() => setIsMinimized(false), 100) - } catch (err) { - console.error("set_hub_minimized FAILED:", err) - setIsMinimized(false) - } - } - - const handleClose = () => { - invoke("close_hub_window").catch((err) => { - console.error("Erro ao fechar janela do hub:", err) - }) - } - - const totalUnread = sessions.reduce((sum, s) => sum + s.unreadCount, 0) - - // Sem token - if (!hasToken) { - return ( -
-
- Token nao configurado -
-
- ) - } - - // Loading - if (isLoading) { - return ( -
-
- - Carregando... -
-
- ) - } - - // Sem sessoes ativas - if (sessions.length === 0) { - return ( -
-
- - Sem chats -
-
- ) - } - - // Minimizado - if (isMinimized) { - return ( -
- -
- ) - } - - // Expandido - return ( -
- {/* Header */} -
-
-
- -
-
-

Chats Ativos

-

- {sessions.length} conversa{sessions.length !== 1 ? "s" : ""} -

-
-
-
- - -
-
- - {/* Lista de sessoes */} -
-
- {sessions.map((session) => ( - handleSelectSession(session.ticketId, session.ticketRef)} - /> - ))} -
-
-
- ) -} - -function SessionItem({ - session, - onClick, -}: { - session: MachineSession - onClick: () => void -}) { - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation() - onClick() - } - - return ( - - ) -} - -function formatRelativeTime(timestamp: number): string { - const now = Date.now() - const diff = now - timestamp - - const minutes = Math.floor(diff / 60000) - if (minutes < 1) return "agora" - if (minutes < 60) return `${minutes}m` - - const hours = Math.floor(minutes / 60) - if (hours < 24) return `${hours}h` - - const days = Math.floor(hours / 24) - return `${days}d` -} diff --git a/apps/desktop/src/chat/ChatWidget.tsx b/apps/desktop/src/chat/ChatWidget.tsx deleted file mode 100644 index c60ae67..0000000 --- a/apps/desktop/src/chat/ChatWidget.tsx +++ /dev/null @@ -1,891 +0,0 @@ -/** - * ChatWidget - Componente de chat em tempo real usando Convex subscriptions - * - * Arquitetura: - * - Usa useQuery do Convex React para subscriptions reativas (tempo real verdadeiro) - * - Usa useMutation do Convex React para enviar mensagens - * - Mantém Tauri apenas para: upload de arquivos, gerenciamento de janela - * - Sem polling - todas as atualizacoes sao push-based via WebSocket - */ - -import { useCallback, useEffect, useMemo, useRef, useState } from "react" -import { open as openDialog } from "@tauri-apps/plugin-dialog" -import { openUrl as openExternal } from "@tauri-apps/plugin-opener" -import { invoke } from "@tauri-apps/api/core" -import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2, Eye, Download, Check, MessagesSquare } from "lucide-react" -import type { Id } from "@convex/_generated/dataModel" -import { useMachineMessages, useMachineSessions, usePostMachineMessage, useMarkMachineMessagesRead, type MachineMessage } from "./useConvexMachineQueries" -import { useConvexMachine } from "./ConvexMachineProvider" - -const MAX_MESSAGES_IN_MEMORY = 200 -const MARK_READ_BATCH_SIZE = 50 -const SCROLL_BOTTOM_THRESHOLD_PX = 120 - -const ALLOWED_EXTENSIONS = [ - "jpg", "jpeg", "png", "gif", "webp", - "pdf", "txt", "doc", "docx", "xls", "xlsx", -] - -interface UploadedAttachment { - storageId: string - name: string - size?: number - type?: string -} - -interface ChatAttachment { - storageId: string - name: string - size?: number - type?: string -} - -function getFileIcon(fileName: string) { - const ext = fileName.toLowerCase().split(".").pop() ?? "" - if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) { - return - } - if (["pdf", "doc", "docx", "txt"].includes(ext)) { - return - } - return -} - -function isImageAttachment(attachment: ChatAttachment) { - if (attachment.type?.startsWith("image/")) return true - const ext = attachment.name.toLowerCase().split(".").pop() ?? "" - return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext) -} - -function formatAttachmentSize(size?: number) { - if (!size) return null - if (size < 1024) return `${size}B` - const kb = size / 1024 - if (kb < 1024) return `${Math.round(kb)}KB` - return `${(kb / 1024).toFixed(1)}MB` -} - -function getUnreadAgentMessageIds(messages: MachineMessage[], unreadCount: number): string[] { - if (unreadCount <= 0 || messages.length === 0) return [] - const ids: string[] = [] - for (let i = messages.length - 1; i >= 0 && ids.length < unreadCount; i--) { - const msg = messages[i] - if (!msg.isFromMachine) { - ids.push(msg.id) - } - } - return ids.reverse() -} - -function chunkArray(items: T[], size: number): T[][] { - if (size <= 0) return [items] - const result: T[][] = [] - for (let i = 0; i < items.length; i += size) { - result.push(items.slice(i, i + size)) - } - return result -} - -function MessageAttachment({ - attachment, - isAgent, - loadUrl, -}: { - attachment: ChatAttachment - isAgent: boolean - loadUrl: (storageId: string) => Promise -}) { - const [url, setUrl] = useState(null) - const [loading, setLoading] = useState(true) - const [downloading, setDownloading] = useState(false) - const [downloaded, setDownloaded] = useState(false) - - useEffect(() => { - let cancelled = false - setLoading(true) - loadUrl(attachment.storageId) - .then((resolved) => { - if (!cancelled) setUrl(resolved) - }) - .catch((err) => { - console.error("Falha ao carregar URL do anexo:", err) - }) - .finally(() => { - if (!cancelled) setLoading(false) - }) - - return () => { - cancelled = true - } - }, [attachment.storageId, loadUrl]) - - const handleView = async () => { - if (!url) return - try { - await openExternal(url) - } catch (err) { - console.error("Falha ao abrir anexo:", err) - } - } - - const handleDownload = async () => { - if (!url || downloading) return - setDownloading(true) - try { - const response = await fetch(url) - const blob = await response.blob() - const downloadUrl = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = downloadUrl - a.download = attachment.name - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(downloadUrl) - setDownloaded(true) - setTimeout(() => setDownloaded(false), 2000) - } catch (err) { - console.error("Falha ao baixar anexo:", err) - await handleView() - } finally { - setDownloading(false) - } - } - - const sizeLabel = formatAttachmentSize(attachment.size) - const isImage = isImageAttachment(attachment) - - if (loading) { - return ( -
- - Carregando anexo... -
- ) - } - - if (isImage && url) { - return ( -
- {/* eslint-disable-next-line @next/next/no-img-element -- Tauri desktop app, not Next.js */} - {attachment.name} -
- - -
-
- ) - } - - return ( -
- {getFileIcon(attachment.name)} - - {sizeLabel && ({sizeLabel})} -
- - -
-
- ) -} - -interface ChatWidgetProps { - ticketId: string - ticketRef?: number -} - -export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) { - const [inputValue, setInputValue] = useState("") - const [isSending, setIsSending] = useState(false) - const [isUploading, setIsUploading] = useState(false) - const [pendingAttachments, setPendingAttachments] = useState([]) - // Inicializa baseado na altura real da janela (< 100px = minimizado) - const [isMinimized, setIsMinimized] = useState(() => window.innerHeight < 100) - - // Convex hooks - const { apiBaseUrl, machineToken } = useConvexMachine() - const { sessions: machineSessions = [] } = useMachineSessions() - const { messages: convexMessages, hasSession, unreadCount, isLoading } = useMachineMessages( - ticketId as Id<"tickets">, - { limit: MAX_MESSAGES_IN_MEMORY } - ) - const postMessage = usePostMachineMessage() - const markMessagesRead = useMarkMachineMessagesRead() - - // Limitar mensagens em memoria - const messages = useMemo(() => convexMessages.slice(-MAX_MESSAGES_IN_MEMORY), [convexMessages]) - - const messagesEndRef = useRef(null) - const messagesContainerRef = useRef(null) - const messageElementsRef = useRef>(new Map()) - const prevHasSessionRef = useRef(false) - - const [isAtBottom, setIsAtBottom] = useState(true) - const isAtBottomRef = useRef(true) - const pendingScrollActionRef = useRef< - | { type: "bottom"; behavior: ScrollBehavior; markRead: boolean } - | { type: "message"; messageId: string; behavior: ScrollBehavior; markRead: boolean } - | null - >(null) - const autoReadInFlightRef = useRef(false) - const lastAutoReadCountRef = useRef(null) - - const unreadAgentMessageIds = useMemo(() => getUnreadAgentMessageIds(messages, unreadCount), [messages, unreadCount]) - const firstUnreadAgentMessageId = unreadAgentMessageIds[0] ?? null - - const otherUnreadCount = useMemo(() => { - if (machineSessions.length <= 1) return 0 - return machineSessions.reduce((sum, session) => { - return sum + (session.ticketId === ticketId ? 0 : session.unreadCount) - }, 0) - }, [machineSessions, ticketId]) - - const handleOpenHub = useCallback(async () => { - try { - await invoke("open_hub_window") - await invoke("set_hub_minimized", { minimized: false }) - } catch (err) { - console.error("Erro ao abrir hub:", err) - } - }, []) - - const updateIsAtBottom = useCallback(() => { - const el = messagesContainerRef.current - if (!el) return - const distance = el.scrollHeight - el.scrollTop - el.clientHeight - const atBottom = distance <= SCROLL_BOTTOM_THRESHOLD_PX - if (isAtBottomRef.current !== atBottom) { - isAtBottomRef.current = atBottom - setIsAtBottom(atBottom) - } - }, []) - - const scrollToBottom = useCallback((behavior: ScrollBehavior) => { - messagesEndRef.current?.scrollIntoView({ behavior }) - requestAnimationFrame(() => updateIsAtBottom()) - }, [updateIsAtBottom]) - - const scrollToMessage = useCallback((messageId: string, behavior: ScrollBehavior) => { - const el = messageElementsRef.current.get(messageId) - if (!el) return false - el.scrollIntoView({ behavior, block: "center" }) - requestAnimationFrame(() => updateIsAtBottom()) - return true - }, [updateIsAtBottom]) - - // Fechar janela quando sessao termina - useEffect(() => { - const prevHasSession = prevHasSessionRef.current - if (prevHasSession && !hasSession) { - invoke("close_chat_window", { ticketId }).catch((err) => { - console.error("Erro ao fechar janela ao encerrar sessao:", err) - }) - } - prevHasSessionRef.current = hasSession - }, [hasSession, ticketId]) - - // Ref para acessar isMinimized dentro de callbacks - const isMinimizedRef = useRef(isMinimized) - useEffect(() => { - isMinimizedRef.current = isMinimized - }, [isMinimized]) - - // Cache de URLs de anexos - const attachmentUrlCacheRef = useRef>(new Map()) - - const loadAttachmentUrl = useCallback(async (storageId: string) => { - const cached = attachmentUrlCacheRef.current.get(storageId) - if (cached) return cached - - if (!apiBaseUrl || !machineToken) { - throw new Error("Configuracao nao disponivel") - } - - const response = await fetch(`${apiBaseUrl}/api/machines/chat/attachments/url`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - machineToken, - ticketId, - storageId, - }), - }) - - if (!response.ok) { - const text = await response.text().catch(() => "") - throw new Error(text || `Falha ao obter URL do anexo (${response.status})`) - } - - const data = (await response.json()) as { url?: string } - if (!data.url) { - throw new Error("Resposta invalida ao obter URL do anexo") - } - - attachmentUrlCacheRef.current.set(storageId, data.url) - return data.url - }, [apiBaseUrl, machineToken, ticketId]) - - const markUnreadMessagesRead = useCallback(async () => { - if (unreadCount <= 0) return false - const ids = getUnreadAgentMessageIds(messages, unreadCount) - if (ids.length === 0) return false - - const chunks = chunkArray(ids, MARK_READ_BATCH_SIZE) - for (const chunk of chunks) { - await markMessagesRead({ - ticketId: ticketId as Id<"tickets">, - messageIds: chunk as Id<"ticketChatMessages">[], - }) - } - return true - }, [messages, ticketId, unreadCount, markMessagesRead]) - - const maybeAutoMarkRead = useCallback(async () => { - if (autoReadInFlightRef.current) return - if (!hasSession || unreadCount <= 0) return - if (isMinimizedRef.current || !isAtBottomRef.current) return - if (lastAutoReadCountRef.current === unreadCount) return - - autoReadInFlightRef.current = true - try { - const didMark = await markUnreadMessagesRead() - if (didMark) { - lastAutoReadCountRef.current = unreadCount - } - } finally { - autoReadInFlightRef.current = false - } - }, [hasSession, unreadCount, markUnreadMessagesRead]) - - // Auto-scroll quando novas mensagens chegam (se ja estava no bottom) - const prevMessagesLengthRef = useRef(messages.length) - useEffect(() => { - if (messages.length > prevMessagesLengthRef.current && isAtBottomRef.current && !isMinimizedRef.current) { - pendingScrollActionRef.current = { type: "bottom", behavior: "smooth", markRead: true } - } - prevMessagesLengthRef.current = messages.length - }, [messages.length]) - - // Executar scroll pendente - useEffect(() => { - if (isMinimized) return - - const action = pendingScrollActionRef.current - if (!action) return - - if (action.type === "bottom") { - if (!messagesEndRef.current) return - pendingScrollActionRef.current = null - scrollToBottom(action.behavior) - if (action.markRead) { - markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err)) - } - return - } - - const ok = scrollToMessage(action.messageId, action.behavior) - if (!ok) { - if (!messagesEndRef.current) return - pendingScrollActionRef.current = null - scrollToBottom(action.behavior) - if (action.markRead) { - markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err)) - } - return - } - - pendingScrollActionRef.current = null - if (action.markRead) { - markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err)) - } - }, [isMinimized, messages, markUnreadMessagesRead, scrollToBottom, scrollToMessage]) - - useEffect(() => { - if (unreadCount === 0) { - lastAutoReadCountRef.current = null - return - } - maybeAutoMarkRead().catch((err) => console.error("Falha ao auto-marcar mensagens:", err)) - }, [isMinimized, isAtBottom, unreadCount, maybeAutoMarkRead]) - - // Sincronizar estado minimizado com tamanho da janela - useEffect(() => { - const mountTime = Date.now() - const STABILIZATION_DELAY = 500 - - const handler = () => { - if (Date.now() - mountTime < STABILIZATION_DELAY) { - return - } - const h = window.innerHeight - setIsMinimized(h < 100) - } - window.addEventListener("resize", handler) - return () => window.removeEventListener("resize", handler) - }, []) - - // Selecionar arquivo para anexar - const handleAttach = async () => { - if (isUploading || isSending) return - - try { - const selected = await openDialog({ - multiple: false, - filters: [{ - name: "Arquivos permitidos", - extensions: ALLOWED_EXTENSIONS, - }], - }) - - if (!selected) return - - const filePath = typeof selected === "string" ? selected : (selected as { path: string }).path - - setIsUploading(true) - - if (!apiBaseUrl || !machineToken) { - throw new Error("Configuracao nao disponivel") - } - - const attachment = await invoke("upload_chat_file", { - baseUrl: apiBaseUrl, - token: machineToken, - filePath, - }) - - setPendingAttachments(prev => [...prev, attachment]) - } catch (err) { - console.error("Erro ao anexar arquivo:", err) - alert(typeof err === "string" ? err : "Erro ao anexar arquivo") - } finally { - setIsUploading(false) - } - } - - // Remover anexo pendente - const handleRemoveAttachment = (storageId: string) => { - setPendingAttachments(prev => prev.filter(a => a.storageId !== storageId)) - } - - // Enviar mensagem - const handleSend = async () => { - if ((!inputValue.trim() && pendingAttachments.length === 0) || isSending) return - - const messageText = inputValue.trim() - const attachmentsToSend = [...pendingAttachments] - setInputValue("") - setPendingAttachments([]) - setIsSending(true) - - try { - await postMessage({ - ticketId: ticketId as Id<"tickets">, - body: messageText, - attachments: attachmentsToSend.length > 0 ? attachmentsToSend.map(a => ({ - storageId: a.storageId as Id<"_storage">, - name: a.name, - size: a.size, - type: a.type, - })) : undefined, - }) - pendingScrollActionRef.current = { type: "bottom", behavior: "smooth", markRead: false } - } catch (err) { - console.error("Erro ao enviar mensagem:", err) - setInputValue(messageText) - setPendingAttachments(attachmentsToSend) - } finally { - setIsSending(false) - } - } - - const handleMinimize = async () => { - setIsMinimized(true) - try { - await invoke("set_chat_minimized", { ticketId, minimized: true }) - } catch (err) { - console.error("Erro ao minimizar janela:", err) - } - } - - const handleExpand = async () => { - if (firstUnreadAgentMessageId) { - pendingScrollActionRef.current = { type: "message", messageId: firstUnreadAgentMessageId, behavior: "auto", markRead: unreadCount > 0 } - } else { - pendingScrollActionRef.current = { type: "bottom", behavior: "auto", markRead: false } - } - - setIsMinimized(false) - try { - await invoke("open_chat_window", { ticketId, ticketRef: ticketRef ?? 0 }) - } catch (err) { - console.error("Erro ao expandir janela:", err) - } - } - - const handleClose = () => { - invoke("close_chat_window", { ticketId }).catch((err) => { - console.error("Erro ao fechar janela de chat:", err) - }) - } - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault() - handleSend() - } - } - - // Loading - if (isLoading) { - return ( -
-
- - Carregando... -
-
- ) - } - - // Sem sessao ativa - if (!hasSession) { - return ( -
-
- - - {ticketRef ? `Ticket #${ticketRef}` : "Chat"} - - - Offline - -
-
- ) - } - - // Minimizado - if (isMinimized) { - return ( -
- -
- ) - } - - // Expandido - return ( -
- {/* Header */} -
-
-
- -
-
-
-

Chat

- - - Online - -
-

- Ticket #{ticketRef} - Suporte -

-
-
-
- {machineSessions.length > 1 && ( - - )} - - -
-
- - {/* Mensagens */} -
- {messages.length === 0 ? ( -
-

- Nenhuma mensagem ainda -

-

- O agente iniciara a conversa em breve -

-
- ) : ( -
- {messages.map((msg) => { - const isAgent = !msg.isFromMachine - const bodyText = msg.body.trim() - const shouldShowBody = - bodyText.length > 0 && !(bodyText === "[Anexo]" && (msg.attachments?.length ?? 0) > 0) - return ( -
- {firstUnreadAgentMessageId === msg.id && unreadCount > 0 && !isAtBottom && ( -
-
- Novas mensagens -
-
- )} - -
{ - if (el) { - messageElementsRef.current.set(msg.id, el) - } else { - messageElementsRef.current.delete(msg.id) - } - }} - className={`flex gap-2 ${isAgent ? "flex-row-reverse" : "flex-row"}`} - > - {/* Avatar */} -
- {isAgent ? : } -
- - {/* Bubble */} -
- {!isAgent && ( -

- {msg.authorName} -

- )} - {shouldShowBody &&

{msg.body}

} - {/* Anexos */} - {msg.attachments && msg.attachments.length > 0 && ( -
- {msg.attachments.map((att) => ( - - ))} -
- )} -

- {formatTime(msg.createdAt)} -

-
-
-
- ) - })} -
-
- )} -
- - {/* Input */} -
- {unreadCount > 0 && !isAtBottom && ( -
- -
- )} - {/* Anexos pendentes */} - {pendingAttachments.length > 0 && ( -
- {pendingAttachments.map((att) => ( -
- {getFileIcon(att.name)} - {att.name} - -
- ))} -
- )} -
-