diff --git a/.angulardoc.json b/.angulardoc.json
new file mode 100644
index 00000000..253388ca
--- /dev/null
+++ b/.angulardoc.json
@@ -0,0 +1,4 @@
+{
+  "repoId": "8f466ce7-4b75-4048-8b8a-cad5bf173aa0",
+  "lastSync": 0
+}
\ No newline at end of file
diff --git a/.earthlyignore b/.earthlyignore
index 37c45b4d..06918d0b 100644
--- a/.earthlyignore
+++ b/.earthlyignore
@@ -12,4 +12,14 @@ desktop/angular/.angular
 
 # Assets are ignored here because the symlink wouldn't work in
 # the buildkit container so we copy the assets directly in Earthfile.
-desktop/angular/assets
\ No newline at end of file
+desktop/angular/assets
+
+
+desktop/tauri/src-tauri/target
+.gitignore
+AUTHORS
+CODE_OF_CONDUCT.md
+LICENSE
+README.md
+TESTING.md
+TRADEMARKS
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 88f8a650..8268448f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,6 @@
 # Compiled binaries
-portmaster
-portmaster.exe
-dnsonly
-dnsonly.exe
-main
-main.exe
-integrationtest
-integrationtest.exe
+*.exe
+dist/
 
 # Dist dir
 dist
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/Earthfile b/Earthfile
index c0da3562..ad1893d9 100644
--- a/Earthfile
+++ b/Earthfile
@@ -1,10 +1,23 @@
-VERSION --arg-scope-and-set 0.8
+VERSION --arg-scope-and-set --global-cache 0.8
 
 ARG --global go_version = 1.21
 ARG --global distro = alpine3.18
 ARG --global node_version = 18
 ARG --global outputDir = "./dist"
 
+# The list of rust targets we support. They will be automatically converted
+# to GOOS, GOARCH and GOARM when building go binaries. See the +RUST_TO_GO_ARCH_STRING
+# helper method at the bottom of the file.
+ARG --global architectures = "x86_64-unknown-linux-gnu" \
+                             "aarch64-unknown-linux-gnu" \
+                             "armv7-unknown-linux-gnueabihf" \
+                             "arm-unknown-linux-gnueabi" \
+                             "x86_64-pc-windows-gnu"
+
+# Import the earthly rust lib since it already provides some useful
+# build-targets and methods to initialize the rust toolchain.
+IMPORT github.com/earthly/lib/rust:3.0.2 AS rust
+
 go-deps:
     FROM golang:${go_version}-${distro}
     WORKDIR /go-workdir
@@ -24,7 +37,6 @@ go-deps:
     COPY go.sum .
     RUN go mod download
 
-
 go-base:
     FROM +go-deps
 
@@ -42,6 +54,15 @@ go-base:
     # ./assets
     COPY assets ./assets
 
+# updates all go dependencies and runs go mod tidy, saving go.mod and go.sum locally.
+update-go-deps:
+    FROM +go-base
+
+    RUN go get -u ./..
+    RUN go mod tidy
+    SAVE ARTIFACT go.mod AS LOCAL go.mod
+    SAVE ARTIFACT --if-exists go.sum AS LOCAL go.sum
+
 # mod-tidy runs 'go mod tidy', saving go.mod and go.sum locally.
 mod-tidy:
     FROM +go-base
@@ -61,6 +82,9 @@ build-go:
     ARG GOARM
     ARG CMDS=portmaster-start portmaster-core hub notifier
 
+    # Get the current version
+    DO +GET_VERSION
+
     CACHE --sharing shared "$GOCACHE"
     CACHE --sharing shared "$GOMODCACHE"
 
@@ -73,20 +97,17 @@ build-go:
 
     # Build all go binaries from the specified in CMDS
     FOR bin IN $CMDS
-        RUN go build -o "/tmp/build/" ./cmds/${bin}
+        RUN go build  -o "/tmp/build/" ./cmds/${bin}
     END
 
-    LET NAME = ""
-
     FOR bin IN $(ls -1 "/tmp/build/")
-        SET NAME = "${outputDir}/${GOOS}_${GOARCH}/${bin}"
-        IF [ "${GOARM}" != "" ]
-            SET NAME = "${outputDir}/${GOOS}_${GOARCH}v${GOARM}/${bin}"
-        END
+        DO +GO_ARCH_STRING --goos="${GOOS}" --goarch="${GOARCH}" --goarm="${GOARM}"
 
-        SAVE ARTIFACT "/tmp/build/${bin}" AS LOCAL "${NAME}"
+        SAVE ARTIFACT "/tmp/build/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${bin}"
     END
 
+    SAVE ARTIFACT "/tmp/build/" ./output
+
 # Test one or more go packages.
 # Run `earthly +test-go` to test all packages
 # Run `earthly +test-go --PKG="service/firewall"` to only test a specific package.
@@ -108,29 +129,19 @@ test-go:
     END
 
 test-go-all-platforms:
-    # Linux platforms:
-    BUILD +test-go --GOARCH=amd64 --GOOS=linux
-    BUILD +test-go --GOARCH=arm64 --GOOS=linux
-    BUILD +test-go --GOARCH=arm --GOOS=linux --GOARM=5
-    BUILD +test-go --GOARCH=arm --GOOS=linux --GOARM=6
-    BUILD +test-go --GOARCH=arm --GOOS=linux --GOARM=7
-
-    # Windows platforms:
-    BUILD +test-go --GOARCH=amd64 --GOOS=windows
-    BUILD +test-go --GOARCH=arm64 --GOOS=windows
+    LOCALLY 
+    FOR arch IN ${architectures}
+        DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
+        BUILD +test-go --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}"
+    END
 
 # Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms
 build-go-release:
-    # Linux platforms:
-    BUILD +build-go --GOARCH=amd64 --GOOS=linux
-    BUILD +build-go --GOARCH=arm64 --GOOS=linux
-    BUILD +build-go --GOARCH=arm --GOOS=linux --GOARM=5
-    BUILD +build-go --GOARCH=arm --GOOS=linux --GOARM=6
-    BUILD +build-go --GOARCH=arm --GOOS=linux --GOARM=7
-
-    # Windows platforms:
-    BUILD +build-go --GOARCH=amd64 --GOOS=windows
-    BUILD +build-go --GOARCH=arm64 --GOOS=windows
+    LOCALLY
+    FOR arch IN ${architectures}
+        DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
+        BUILD +build-go --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}"
+    END
 
 # Builds all binaries from the cmds/ folder for linux/windows AMD64
 # Most utility binaries are never needed on other platforms.
@@ -187,13 +198,199 @@ angular-project:
 # Build the angular projects (portmaster-UI and tauri-builtin) in production mode
 angular-release:
     BUILD +angular-project --project=portmaster --dist=./dist --configuration=production --baseHref=/ui/modules/portmaster
-    BUILD +angular-project --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/"
 
 # Build the angular projects (portmaster-UI and tauri-builtin) in dev mode
 angular-dev:
     BUILD +angular-project --project=portmaster --dist=./dist --configuration=development --baseHref=/ui/modules/portmaster
-    BUILD +angular-project --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=development --baseHref="/"
+
+# A base target for rust to prepare the build container
+rust-base:
+    FROM rust:1.76-bookworm
+
+    RUN apt-get update -qq
+    RUN apt-get install --no-install-recommends -qq \
+        autoconf \
+        autotools-dev \
+        libtool-bin \
+        clang \
+        cmake \
+        bsdmainutils \
+        g++-mingw-w64-x86-64 \
+        gcc-aarch64-linux-gnu \
+        gcc-arm-none-eabi \
+        gcc-arm-linux-gnueabi \
+        gcc-arm-linux-gnueabihf \
+        libgtk-3-dev \
+        libjavascriptcoregtk-4.1-dev \
+        libsoup-3.0-dev \
+        libwebkit2gtk-4.1-dev \
+        build-essential \
+        curl \
+        wget \
+        file \
+        libssl-dev \
+        libayatana-appindicator3-dev \
+        librsvg2-dev
+
+    # Add some required rustup components
+    RUN rustup component add clippy
+    RUN rustup component add rustfmt
+
+    # Install toolchains and targets
+    FOR arch IN ${architectures}
+        RUN rustup target add ${arch}
+    END
+
+    DO rust+INIT --keep_fingerprints=true
+
+    # For now we need tauri-cli 1.5 for bulding
+    DO rust+CARGO --args="install tauri-cli --version ^1.5.11"
+
+tauri-src:
+    FROM +rust-base
+
+    WORKDIR /app/tauri
+
+    # --keep-ts is necessary to ensure that the timestamps of the source files
+    # are preserved such that Rust's incremental compilation works correctly.
+    COPY --keep-ts ./desktop/tauri/ .
+    COPY assets/data ./assets
+    COPY (+angular-project/dist/tauri-builtin --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/") ./../angular/dist/tauri-builtin
+
+build-tauri:
+    FROM +tauri-src
+
+    ARG --required target
+    ARG output="release/[^\./]+"
+    ARG bundle="none"
+
+    # if we want tauri to create the installer bundles we also need to provide all external binaries
+    # we need to do some magic here because tauri expects the binaries to include the rust target tripple.
+    # We already knwo that triple because it's a required argument. From that triple, we use +RUST_TO_GO_ARCH_STRING
+    # function from below to parse the triple and guess wich GOOS and GOARCH we need.
+    IF [ "${bundle}" != "none" ] 
+        RUN mkdir /tmp/gobuild
+        RUN mkdir ./binaries
+
+        DO +RUST_TO_GO_ARCH_STRING --rustTarget="${target}"
+        RUN echo "GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} GO_ARCH_STRING=${GO_ARCH_STRING}"
+
+        COPY (+build-go/output --GOOS="${GOOS}" --CMDS="portmaster-start portmaster-core" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
+
+        LET dest=""
+        FOR bin IN $(ls /tmp/gobuild)
+            SET dest="./binaries/${bin}-${target}"
+
+            IF [ -z "${bin##*.exe}" ]
+                SET dest = "./binaries/${bin%.*}-${target}.exe"
+            END
+
+            RUN echo "Copying ${bin} to ${dest}"
+            RUN cp "/tmp/gobuild/${bin}" "${dest}"
+        END
+    END
+
+    # The following is exected to work but doesn't. for whatever reason cargo-sweep errors out on the windows-toolchain.
+    #
+    #   DO rust+CARGO --args="tauri build --bundles none --ci --target=${target}" --output="release/[^/\.]+"
+    #
+    # For, now, we just directly mount the rust target cache and call cargo ourself.
+
+    DO rust+SET_CACHE_MOUNTS_ENV
+    RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --bundles "${bundle}" --ci --target="${target}"
+    DO rust+COPY_OUTPUT --output="${output}"
+
+    RUN ls target
+
+tauri-release:
+    LOCALLY
+
+    ARG bundle="none"
+
+    FOR arch IN ${architectures}
+        BUILD +build-tauri --target="${arch}" --bundle="${bundle}"
+    END
 
 release:
     BUILD +build-go-release
     BUILD +angular-release
+
+
+# Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names.
+# in the form of ${GOOS}_{GOARCH} if GOARM is empty, otherwise ${GOOS}_${GOARCH}v${GOARM}.
+# Thats the same format as expected and served by our update server.
+#
+# The result is available as GO_ARCH_STRING environment variable in the build context.
+GO_ARCH_STRING:
+    FUNCTION
+    ARG --required goos
+    ARG --required goarch
+    ARG goarm
+
+    LET result = "${goos}_${goarch}"
+    IF [ "${goarm}" != "" ]
+        SET result = "${goos}_${goarch}v${goarm}"
+    END
+
+    ENV GO_ARCH_STRING="${result}"
+
+# Takes a rust target (--rustTarget) and extracts architecture and OS and arm version
+# and finally calls GO_ARCH_STRING.
+#
+# The result is available as GO_ARCH_STRING environment variable in the build context.
+# It also exports GOOS, GOARCH and GOARM environment variables.
+RUST_TO_GO_ARCH_STRING:
+    FUNCTION
+    ARG --required rustTarget
+
+    LET goos=""
+    IF [ -z "${rustTarget##*linux*}" ]
+        SET goos="linux"
+    ELSE
+        SET goos="windows"
+    END
+
+
+    LET goarch=""
+    LET goarm=""
+
+    IF [ -z "${rustTarget##*x86_64*}" ]
+        SET goarch="amd64"
+    ELSE IF [ -z "${rustTarget##*arm*}" ]
+        SET goarch="arm"
+        SET goarm="6"
+
+        IF [ -z "${rustTarget##*v7*}" ]
+            SET goarm="7"
+        END
+    ELSE IF [ -z "${rustTarget##*aarch64*}" ]
+        SET goarch="arm64"
+    ELSE
+        RUN echo "GOARCH not detected"; \
+            exit 1;
+    END
+
+    ENV GOOS="${goos}"
+    ENV GOARCH="${goarch}"
+    ENV GOARM="${goarm}"
+
+    DO +GO_ARCH_STRING --goos="${goos}" --goarch="${goarch}" --goarm="${goarm}"
+
+GET_VERSION:
+    FUNCTION
+    LOCALLY
+
+    LET VERSION=$(git tag --points-at)
+    IF [ -z "${VERSION}"]
+        SET VERSION=$(git describe --tags --abbrev=0)§dev§build
+    ELSE IF ! git diff --quite
+        SET VERSION="${VERSION}§dev§build"
+    END
+
+    RUN echo "Version is ${VERSION}"
+    ENV VERSION="${VERSION}"
+
+test:
+    LOCALLY
+
+    DO +GET_VERSION
\ No newline at end of file
diff --git a/desktop/angular/package.json b/desktop/angular/package.json
index 4131e18f..21207615 100644
--- a/desktop/angular/package.json
+++ b/desktop/angular/package.json
@@ -15,7 +15,7 @@
     "chrome-extension": "NODE_ENV=production ng build --configuration production portmaster-chrome-extension",
     "chrome-extension:dev": "ng build --configuration development portmaster-chrome-extension --watch",
     "build": "npm run build-libs && NODE_ENV=production ng build --configuration production --base-href /ui/modules/portmaster/",
-    "build-tauri": "npm run build-libs && NODE_ENV=production ng build --configuration production",
+    "build-tauri": "npm run build-libs && NODE_ENV=production ng build --configuration production tauri-builtin",
     "serve-tauri-builtin": "ng serve tauri-builtin --port 4100",
     "serve-app": "ng serve --port 4200 --proxy-config ./proxy.json",
     "tauri-dev": "npm install && run-s build-libs:dev && run-p serve-app serve-tauri-builtin"
diff --git a/desktop/tauri/.gitkeep b/desktop/tauri/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/desktop/tauri/assets b/desktop/tauri/assets
new file mode 120000
index 00000000..21dab851
--- /dev/null
+++ b/desktop/tauri/assets
@@ -0,0 +1 @@
+../../assets/data
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/.gitignore b/desktop/tauri/src-tauri/.gitignore
new file mode 100644
index 00000000..aba21e24
--- /dev/null
+++ b/desktop/tauri/src-tauri/.gitignore
@@ -0,0 +1,3 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
diff --git a/desktop/tauri/src-tauri/Cargo.lock b/desktop/tauri/src-tauri/Cargo.lock
new file mode 100644
index 00000000..a0bda3a8
--- /dev/null
+++ b/desktop/tauri/src-tauri/Cargo.lock
@@ -0,0 +1,7286 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.2.11",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+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 = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[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 = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "app"
+version = "0.1.0"
+dependencies = [
+ "assert_matches",
+ "cached",
+ "ctor",
+ "dataurl",
+ "dirs",
+ "futures-util",
+ "gdk-pixbuf",
+ "gdk-pixbuf-sys",
+ "gio-sys 0.18.1",
+ "glib 0.18.4",
+ "glib-sys 0.18.1",
+ "gtk",
+ "gtk-sys",
+ "http 1.0.0",
+ "lazy_static",
+ "log",
+ "notify-rust",
+ "pretty_env_logger",
+ "rust-ini",
+ "serde",
+ "serde_json",
+ "sha",
+ "tauri",
+ "tauri-build",
+ "tauri-cli",
+ "tauri-plugin-cli",
+ "tauri-plugin-clipboard-manager",
+ "tauri-plugin-dialog",
+ "tauri-plugin-notification",
+ "tauri-plugin-os",
+ "tauri-plugin-shell",
+ "tauri-plugin-single-instance",
+ "thiserror",
+ "tokio",
+ "tokio-websockets",
+ "url",
+ "uuid",
+ "which",
+ "windows 0.54.0",
+ "windows-service",
+]
+
+[[package]]
+name = "ar"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69"
+
+[[package]]
+name = "arboard"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08"
+dependencies = [
+ "clipboard-win",
+ "core-graphics 0.22.3",
+ "image",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "parking_lot",
+ "thiserror",
+ "winapi",
+ "x11rb",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
+[[package]]
+name = "async-broadcast"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
+dependencies = [
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 4.0.0",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
+dependencies = [
+ "async-lock 3.2.0",
+ "async-task",
+ "concurrent-queue",
+ "fastrand 2.0.1",
+ "futures-lite 2.1.0",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.27",
+ "slab",
+ "socket2 0.4.10",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
+dependencies = [
+ "async-lock 3.2.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.1.0",
+ "parking",
+ "polling 3.3.1",
+ "rustix 0.38.30",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c"
+dependencies = [
+ "event-listener 4.0.0",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
+dependencies = [
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.30",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io 2.2.2",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.30",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-task"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1"
+
+[[package]]
+name = "async-trait"
+version = "0.1.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "atk"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4"
+dependencies = [
+ "atk-sys",
+ "glib 0.18.4",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "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 = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "axum"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "base64 0.21.5",
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-util",
+ "http 0.2.11",
+ "http-body",
+ "hyper",
+ "itoa 1.0.10",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sha1",
+ "sync_wrapper",
+ "tokio",
+ "tokio-tungstenite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 0.2.11",
+ "http-body",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "blake2b_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[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 = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel",
+ "async-lock 3.2.0",
+ "async-task",
+ "fastrand 2.0.1",
+ "futures-io",
+ "futures-lite 2.1.0",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "brotli"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bswap"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3acc5ce9c60e68df21b877f13f908ef95c89f01cb6c656cf76ba95f10bc72f5"
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytecount"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cached"
+version = "0.46.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c8c50262271cdf5abc979a5f76515c234e764fa025d1ba4862c0f0bcda0e95"
+dependencies = [
+ "ahash",
+ "cached_proc_macro",
+ "cached_proc_macro_types",
+ "hashbrown 0.14.3",
+ "instant",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "cached_proc_macro"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c878c71c2821aa2058722038a59a67583a4240524687c6028571c9b395ded61f"
+dependencies = [
+ "darling 0.14.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "cached_proc_macro_types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
+
+[[package]]
+name = "cairo-rs"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f33613627f0dea6a731b0605101fad59ba4f193a52c96c4687728d822605a8a1"
+dependencies = [
+ "bitflags 2.4.1",
+ "cairo-sys-rs",
+ "glib 0.18.4",
+ "libc",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+dependencies = [
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d1ece59890e746567b467253aea0adbe8a21784d0b025d8a306f66c391c2957"
+dependencies = [
+ "serde",
+ "toml 0.8.2",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "chrono"
+version = "0.4.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags 1.3.2",
+ "strsim 0.8.0",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "clap"
+version = "4.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim 0.10.0",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c"
+dependencies = [
+ "clap 4.4.11",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "clipboard-win"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
+dependencies = [
+ "error-code",
+ "str-buf",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics 0.23.1",
+ "foreign-types 0.5.0",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "common-path"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "const-random"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
+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.11",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[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-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.3.2",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.5.0",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset 0.9.0",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa 0.4.8",
+ "matches",
+ "phf 0.8.0",
+ "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.52",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
+dependencies = [
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "ctrlc"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b"
+dependencies = [
+ "nix 0.27.1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core 0.14.4",
+ "darling_macro 0.14.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
+dependencies = [
+ "darling_core 0.20.3",
+ "darling_macro 0.20.3",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core 0.14.4",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
+dependencies = [
+ "darling_core 0.20.3",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "dataurl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17a1f14ed857323d318ca723a05a456196347efbe855f712f68cf6b8a14f8f15"
+dependencies = [
+ "atty",
+ "base64 0.13.1",
+ "clap 2.34.0",
+ "encoding_rs",
+ "percent-encoding",
+ "url",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "thiserror",
+ "zeroize",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
+dependencies = [
+ "libc",
+ "redox_users 0.3.5",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users 0.4.4",
+ "winapi",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[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 = "dtoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "embed-resource"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2"
+dependencies = [
+ "cc",
+ "rustc_version",
+ "toml 0.8.2",
+ "vswhom",
+ "winreg 0.51.0",
+]
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
+dependencies = [
+ "libc",
+ "str-buf",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "exr"
+version = "1.72.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
+dependencies = [
+ "bit_field",
+ "flume",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868"
+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 0.9.0",
+ "rustc_version",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flume"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+dependencies = [
+ "spin",
+]
+
+[[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.52",
+]
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fraction"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678"
+dependencies = [
+ "lazy_static",
+ "num",
+]
+
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[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-channel"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-lite"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
+dependencies = [
+ "fastrand 2.0.1",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+
+[[package]]
+name = "futures-task"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+
+[[package]]
+name = "futures-util"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+dependencies = [
+ "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 = "gdk"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646"
+dependencies = [
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib 0.18.4",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446f32b74d22c33b7b258d4af4ffde53c2bf96ca2e29abdf1a785fe59bd6c82c"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib 0.18.4",
+ "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 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkwayland-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a"
+dependencies = [
+ "gdk-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkx11"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2ea8a4909d530f79921290389cbd7c34cb9d623bfe970eaae65ca5f9cd9cce"
+dependencies = [
+ "gdk",
+ "gdkx11-sys",
+ "gio",
+ "glib 0.18.4",
+ "libc",
+ "x11",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043"
+dependencies = [
+ "gdk-sys",
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps",
+ "x11",
+]
+
+[[package]]
+name = "generator"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "windows 0.48.0",
+]
+
+[[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 = "gethostname"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
+dependencies = [
+ "libc",
+ "windows-targets 0.48.5",
+]
+
+[[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.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[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 0.18.1",
+ "glib 0.18.4",
+ "libc",
+ "once_cell",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229"
+dependencies = [
+ "glib-sys 0.16.3",
+ "gobject-sys 0.16.3",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "glib"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba"
+dependencies = [
+ "bitflags 1.3.2",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.16.3",
+ "glib-macros 0.16.8",
+ "glib-sys 0.16.3",
+ "gobject-sys 0.16.3",
+ "libc",
+ "once_cell",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "glib"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "951bbd7fdc5c044ede9f05170f05a3ae9479239c3afdfe2d22d537a3add15c4e"
+dependencies = [
+ "bitflags 2.4.1",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.18.1",
+ "glib-macros 0.18.3",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "memchr",
+ "once_cell",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.16.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b"
+dependencies = [
+ "anyhow",
+ "heck",
+ "proc-macro-crate 1.3.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72793962ceece3863c2965d7f10c8786323b17c7adea75a515809fa20ab799a5"
+dependencies = [
+ "heck",
+ "proc-macro-crate 2.0.0",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata 0.4.3",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1"
+dependencies = [
+ "glib-sys 0.16.3",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
+dependencies = [
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c"
+dependencies = [
+ "atk",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib 0.18.4",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.11",
+ "indexmap 2.1.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "handlebars"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225"
+dependencies = [
+ "log",
+ "pest",
+ "pest_derive",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "http"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.10",
+]
+
+[[package]]
+name = "http"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.10",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.11",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.11",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 1.0.10",
+ "pin-project-lite",
+ "socket2 0.4.10",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core 0.51.1",
+]
+
+[[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.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae"
+dependencies = [
+ "byteorder",
+ "png",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata 0.4.3",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "image"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-rational",
+ "num-traits",
+ "png",
+ "qoi",
+ "tiff",
+]
+
+[[package]]
+name = "include_dir"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
+dependencies = [
+ "include_dir_macros",
+]
+
+[[package]]
+name = "include_dir_macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[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.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+ "serde",
+]
+
+[[package]]
+name = "infer"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "infer"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[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-terminal"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[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 = "iso8601"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[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 0.18.4",
+ "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 0.18.1",
+ "gobject-sys 0.18.0",
+ "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",
+ "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 = "jobserver"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
+dependencies = [
+ "serde",
+ "serde_json",
+ "thiserror",
+ "treediff",
+]
+
+[[package]]
+name = "json5"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "serde",
+]
+
+[[package]]
+name = "jsonschema"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978"
+dependencies = [
+ "ahash",
+ "anyhow",
+ "base64 0.21.5",
+ "bytecount",
+ "clap 4.4.11",
+ "fancy-regex",
+ "fraction",
+ "getrandom 0.2.11",
+ "iso8601",
+ "itoa 1.0.10",
+ "memchr",
+ "num-cmp",
+ "once_cell",
+ "parking_lot",
+ "percent-encoding",
+ "regex",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "time",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
+dependencies = [
+ "bitflags 2.4.1",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "kuchikiki"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
+dependencies = [
+ "cssparser",
+ "html5ever",
+ "indexmap 1.9.3",
+ "matches",
+ "selectors",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
+[[package]]
+name = "libappindicator"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a"
+dependencies = [
+ "glib 0.18.4",
+ "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.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "line-wrap"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
+dependencies = [
+ "safemem",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+dependencies = [
+ "value-bag",
+]
+
+[[package]]
+name = "loom"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64"
+dependencies = [
+ "cc",
+ "dirs-next",
+ "objc-foundation",
+ "objc_id",
+ "time",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "markup5ever"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
+dependencies = [
+ "log",
+ "phf 0.10.1",
+ "phf_codegen 0.10.0",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "md5"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "minisign"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b23ef13ff1d745b1e52397daaa247e333c607f3cff96d4df2b798dc252db974b"
+dependencies = [
+ "getrandom 0.2.11",
+ "rpassword",
+ "scrypt",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "muda"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b564d551449738387fb4541aef5fbfceaa81b2b732f2534c1c7c89dc7d673eaa"
+dependencies = [
+ "cocoa",
+ "crossbeam-channel",
+ "gtk",
+ "keyboard-types",
+ "objc",
+ "once_cell",
+ "png",
+ "serde",
+ "thiserror",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ndk"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
+dependencies = [
+ "bitflags 1.3.2",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror",
+]
+
+[[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.4.1+23.1.7779620"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+]
+
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "notify"
+version = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
+dependencies = [
+ "bitflags 2.4.1",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "walkdir",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "notify-debouncer-mini"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
+dependencies = [
+ "crossbeam-channel",
+ "log",
+ "notify",
+]
+
+[[package]]
+name = "notify-rust"
+version = "4.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226"
+dependencies = [
+ "log",
+ "mac-notification-sys",
+ "serde",
+ "tauri-winrt-notification",
+ "zbus",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-cmp"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
+
+[[package]]
+name = "num-complex"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "open"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a083c0c7e5e4a8ec4176346cf61f67ac674e8bfb059d9226e1c54a96b377c12"
+dependencies = [
+ "is-wsl",
+ "libc",
+ "pathdiff",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ordered-multimap"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4d6a8c22fc714f0c2373e6091bf6f5e9b37b1bc0b1184874b7e0a4e303d318f"
+dependencies = [
+ "dlv-list",
+ "hashbrown 0.14.3",
+]
+
+[[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 = "os_info"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
+dependencies = [
+ "log",
+ "serde",
+ "winapi",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pango"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
+dependencies = [
+ "gio",
+ "glib 0.18.4",
+ "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 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pest"
+version = "2.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_macros 0.11.2",
+ "phf_shared 0.11.2",
+]
+
+[[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.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+]
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared 0.11.2",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
+dependencies = [
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "plist"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
+dependencies = [
+ "base64 0.21.5",
+ "indexmap 2.1.0",
+ "line-wrap",
+ "quick-xml 0.31.0",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "png"
+version = "0.17.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix 0.38.30",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[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.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "pretty_env_logger"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
+dependencies = [
+ "env_logger",
+ "log",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
+dependencies = [
+ "toml_edit 0.20.2",
+]
+
+[[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.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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_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_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.11",
+]
+
+[[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 = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
+[[package]]
+name = "rayon"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+dependencies = [
+ "getrandom 0.1.16",
+ "redox_syscall 0.1.57",
+ "rust-argon2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom 0.2.11",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.3",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "reqwest"
+version = "0.11.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
+dependencies = [
+ "base64 0.21.5",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.11",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "system-configuration",
+ "tokio",
+ "tokio-util",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "winreg 0.50.0",
+]
+
+[[package]]
+name = "rfd"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241a0deb168c88050d872294f7b3106c1dfa8740942bcc97bc91b98e97b5c501"
+dependencies = [
+ "block",
+ "dispatch",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "gtk-sys",
+ "js-sys",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+dependencies = [
+ "cc",
+ "getrandom 0.2.11",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rpassword"
+version = "7.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
+dependencies = [
+ "libc",
+ "rtoolbox",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rtoolbox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rust-argon2"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+dependencies = [
+ "base64 0.13.1",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "rust-ini"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7673e0aa20ee4937c6aacfc12bb8341cfbf054cdd21df6bec5fd0629fe9339b"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
+[[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 = "schemars"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+ "url",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 1.0.109",
+]
+
+[[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 = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "pbkdf2 0.12.2",
+ "salsa20",
+ "sha2",
+]
+
+[[package]]
+name = "selectors"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
+dependencies = [
+ "bitflags 1.3.2",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "matches",
+ "phf 0.8.0",
+ "phf_codegen 0.8.0",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+ "thin-slice",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+
+[[package]]
+name = "serde"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_fmt"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa 1.0.10",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa 1.0.10",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa 1.0.10",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
+dependencies = [
+ "base64 0.21.5",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.1.0",
+ "serde",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
+dependencies = [
+ "darling 0.20.3",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
+dependencies = [
+ "nodrop",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4208d5a903276a9f3b797afdf6c5bc12a8da1344b053b100abf3565ecc80cb7e"
+dependencies = [
+ "bswap",
+]
+
+[[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 = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+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 = "shared_child"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+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 = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "socks"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
+dependencies = [
+ "byteorder",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "soup3"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
+dependencies = [
+ "futures-channel",
+ "gio",
+ "glib 0.18.4",
+ "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 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "state"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8"
+dependencies = [
+ "loom",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str-buf"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
+
+[[package]]
+name = "string_cache"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared 0.10.0",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "sval"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82a2386bea23a121e4e72450306b1dd01078b6399af11b93897bf84640a28a59"
+
+[[package]]
+name = "sval_buffer"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16c047898a0e19002005512243bc9ef1c1037aad7d03d6c594e234efec80795"
+dependencies = [
+ "sval",
+ "sval_ref",
+]
+
+[[package]]
+name = "sval_dynamic"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74fb116e2ecdcb280b0108aa2ee4434df50606c3208c47ac95432730eaac20c"
+dependencies = [
+ "sval",
+]
+
+[[package]]
+name = "sval_fmt"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10837b4f0feccef271b2b1c03784e08f6d0bb6d23272ec9e8c777bfadbb8f1b8"
+dependencies = [
+ "itoa 1.0.10",
+ "ryu",
+ "sval",
+]
+
+[[package]]
+name = "sval_json"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891f5ecdf34ce61a8ab2d10f9cfdc303347b0afec4dad6702757419d2d8312a9"
+dependencies = [
+ "itoa 1.0.10",
+ "ryu",
+ "sval",
+]
+
+[[package]]
+name = "sval_nested"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63fcffb4b79c531f38e3090788b64f3f4d54a180aacf02d69c42fa4e4bf284c3"
+dependencies = [
+ "sval",
+ "sval_buffer",
+ "sval_ref",
+]
+
+[[package]]
+name = "sval_ref"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af725f9c2aa7cec4ca9c47da2cc90920c4c82d3fa537094c66c77a5459f5809d"
+dependencies = [
+ "sval",
+]
+
+[[package]]
+name = "sval_serde"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7589c649a03d21df40b9a926787d2c64937fa1dccec8d87c6cd82989a2e0a4"
+dependencies = [
+ "serde",
+ "sval",
+ "sval_nested",
+]
+
+[[package]]
+name = "swift-rs"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bbdb58577b6301f8d17ae2561f32002a5bae056d444e0f69e611e504a276204"
+dependencies = [
+ "base64 0.21.5",
+ "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.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "sys-locale"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml 0.8.2",
+ "version-compare",
+]
+
+[[package]]
+name = "tao"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c0dff18fed076d29cb5779e918ef4b8a5dbb756204e4a027794f0bce233d949"
+dependencies = [
+ "bitflags 1.3.2",
+ "cc",
+ "cocoa",
+ "core-foundation",
+ "core-graphics 0.23.1",
+ "crossbeam-channel",
+ "dispatch",
+ "gdkwayland-sys",
+ "gdkx11-sys",
+ "gtk",
+ "image",
+ "instant",
+ "jni",
+ "lazy_static",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "parking_lot",
+ "png",
+ "raw-window-handle",
+ "scopeguard",
+ "tao-macros",
+ "unicode-segmentation",
+ "url",
+ "windows 0.52.0",
+ "windows-implement",
+ "windows-version",
+ "x11-dl",
+ "zbus",
+]
+
+[[package]]
+name = "tao-macros"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
+
+[[package]]
+name = "tauri"
+version = "2.0.0-alpha.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05fb63873c39d3fd5ddad995d395e7b7394ece0b69aeacb31e91d24af48f3de1"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "cocoa",
+ "dirs-next",
+ "embed_plist",
+ "futures-util",
+ "getrandom 0.2.11",
+ "glob",
+ "gtk",
+ "heck",
+ "http 0.2.11",
+ "ico",
+ "infer 0.15.0",
+ "jni",
+ "libc",
+ "log",
+ "mime",
+ "muda",
+ "objc",
+ "percent-encoding",
+ "png",
+ "raw-window-handle",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "state",
+ "swift-rs",
+ "tauri-build",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils 2.0.0-alpha.12",
+ "thiserror",
+ "tokio",
+ "tray-icon",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "window-vibrancy",
+ "windows 0.52.0",
+]
+
+[[package]]
+name = "tauri-build"
+version = "2.0.0-alpha.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a2582ffb43e5c28932c43ffc40c295a9196a9a33ffb1163269c6baed84834a"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "dirs-next",
+ "heck",
+ "json-patch",
+ "plist",
+ "semver",
+ "serde",
+ "serde_json",
+ "swift-rs",
+ "tauri-utils 2.0.0-alpha.12",
+ "tauri-winres",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-bundler"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "657d0d0b2e820978ee51109bd75f03cee23dc1a83388d08f82fd368635c14e04"
+dependencies = [
+ "anyhow",
+ "ar",
+ "dirs-next",
+ "dunce",
+ "flate2",
+ "glob",
+ "handlebars",
+ "heck",
+ "hex",
+ "image",
+ "log",
+ "md5",
+ "os_pipe",
+ "plist",
+ "regex",
+ "semver",
+ "serde",
+ "serde_json",
+ "sha1",
+ "sha2",
+ "strsim 0.10.0",
+ "tar",
+ "tauri-icns",
+ "tauri-utils 1.5.3",
+ "tempfile",
+ "thiserror",
+ "time",
+ "ureq",
+ "uuid",
+ "walkdir",
+ "windows-sys 0.48.0",
+ "winreg 0.51.0",
+ "zip",
+]
+
+[[package]]
+name = "tauri-cli"
+version = "1.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90b2a88fd572b14c0ca224675aaad590e38d95e53846df55459edbdc910795eb"
+dependencies = [
+ "anyhow",
+ "axum",
+ "base64 0.21.5",
+ "cc",
+ "clap 4.4.11",
+ "clap_complete",
+ "colored",
+ "common-path",
+ "ctrlc",
+ "dialoguer",
+ "env_logger",
+ "glob",
+ "handlebars",
+ "heck",
+ "html5ever",
+ "ignore",
+ "image",
+ "include_dir",
+ "itertools",
+ "json-patch",
+ "jsonschema",
+ "kuchikiki",
+ "libc",
+ "log",
+ "minisign",
+ "notify",
+ "notify-debouncer-mini",
+ "once_cell",
+ "os_info",
+ "os_pipe",
+ "regex",
+ "semver",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "shared_child",
+ "tauri-bundler",
+ "tauri-icns",
+ "tauri-utils 1.5.3",
+ "tokio",
+ "toml 0.8.2",
+ "toml_edit 0.21.1",
+ "unicode-width",
+ "ureq",
+ "url",
+ "winapi",
+ "zeroize",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "2.0.0-alpha.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06976ec7b704d6b842169ffd4ce596e9ce45917a0ab462cb96a119fa2829be9"
+dependencies = [
+ "base64 0.21.5",
+ "brotli",
+ "ico",
+ "json-patch",
+ "plist",
+ "png",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "serde",
+ "serde_json",
+ "sha2",
+ "tauri-utils 2.0.0-alpha.12",
+ "thiserror",
+ "time",
+ "url",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-icns"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03b7eb4d0d43724ba9ba6a6717420ee68aee377816a3edbb45db8c18862b1431"
+dependencies = [
+ "byteorder",
+ "png",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "2.0.0-alpha.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff509be5a5ac34ec2e60d9029af1032c0a33e421f3e823bc92695192e2871c17"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+ "tauri-codegen",
+ "tauri-utils 2.0.0-alpha.12",
+]
+
+[[package]]
+name = "tauri-plugin-cli"
+version = "2.0.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af3ce9173dd3ae43c5c7529cce495e89cc9d8773adcd2a3e0efb5123aa052c64"
+dependencies = [
+ "clap 4.4.11",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-clipboard-manager"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32d537328bba01bcbbff4fc7daa1175744afdd42e554b6c897d9a1b1f76b023"
+dependencies = [
+ "arboard",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-dialog"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ead9b1276ed45ffec0a27ff51239614fa9b462a7483f5cb98f0c555a40754e9"
+dependencies = [
+ "glib 0.16.9",
+ "log",
+ "raw-window-handle",
+ "rfd",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-fs",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-fs"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c81479fdc92bab8609d896249e016404f9fac24a27ddf66e1daafd4db1a35"
+dependencies = [
+ "anyhow",
+ "glob",
+ "serde",
+ "tauri",
+ "thiserror",
+ "uuid",
+]
+
+[[package]]
+name = "tauri-plugin-notification"
+version = "2.0.0-alpha.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e1cfe331495d0e72b9d48191eec98a54f9e189571b8ec6affb39b90b3df3bc"
+dependencies = [
+ "log",
+ "notify-rust",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-os"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dfd82d59cdf0229ffe62d38e12bdfee053c4f915883afe6f982b672a7e28d44"
+dependencies = [
+ "gethostname 0.4.3",
+ "log",
+ "os_info",
+ "serde",
+ "serde_json",
+ "serialize-to-javascript",
+ "sys-locale",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-shell"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdf84ccb3f5ac4df2dfeb5e2f09b9048d8633d9b98d72c701aba72642790f2d9"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "open",
+ "os_pipe",
+ "regex",
+ "serde",
+ "serde_json",
+ "shared_child",
+ "tauri",
+ "thiserror",
+]
+
+[[package]]
+name = "tauri-plugin-single-instance"
+version = "2.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d25b229dea0a7cb72ab43ebd17fa7479eda058678bead1ecca431013d5e5ebf"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+ "windows-sys 0.52.0",
+ "zbus",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "1.0.0-alpha.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a989e58af6e554dbac798a0a8d112faafc1509bcfab626466181e0724f09c5"
+dependencies = [
+ "gtk",
+ "http 0.2.11",
+ "jni",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils 2.0.0-alpha.12",
+ "thiserror",
+ "url",
+ "windows 0.52.0",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "1.0.0-alpha.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9f181a6f5f982204ae293c19f37ba90116b8ec0bfd0a08c7a7ba67200cd9e3"
+dependencies = [
+ "cocoa",
+ "gtk",
+ "http 0.2.11",
+ "jni",
+ "percent-encoding",
+ "raw-window-handle",
+ "tao",
+ "tauri-runtime",
+ "tauri-utils 2.0.0-alpha.12",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.52.0",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21"
+dependencies = [
+ "aes-gcm",
+ "ctor",
+ "dunce",
+ "getrandom 0.2.11",
+ "glob",
+ "heck",
+ "html5ever",
+ "infer 0.13.0",
+ "json-patch",
+ "json5",
+ "kuchikiki",
+ "log",
+ "memchr",
+ "phf 0.11.2",
+ "schemars",
+ "semver",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "serialize-to-javascript",
+ "thiserror",
+ "toml 0.7.8",
+ "url",
+ "walkdir",
+ "windows-version",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "2.0.0-alpha.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4858f99fc9f28b72008ef51d04d18b7e3646845c2bc18ee340045fed6ed5095"
+dependencies = [
+ "brotli",
+ "ctor",
+ "dunce",
+ "glob",
+ "heck",
+ "html5ever",
+ "infer 0.15.0",
+ "json-patch",
+ "kuchikiki",
+ "log",
+ "memchr",
+ "phf 0.11.2",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "thiserror",
+ "url",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-winres"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb"
+dependencies = [
+ "embed-resource",
+ "toml 0.7.8",
+]
+
+[[package]]
+name = "tauri-winrt-notification"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2"
+dependencies = [
+ "quick-xml 0.30.0",
+ "windows 0.51.1",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+dependencies = [
+ "cfg-if",
+ "fastrand 2.0.1",
+ "redox_syscall 0.4.1",
+ "rustix 0.38.30",
+ "windows-sys 0.48.0",
+]
+
+[[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 = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thin-slice"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
+
+[[package]]
+name = "thiserror"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tiff"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
+[[package]]
+name = "time"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+dependencies = [
+ "deranged",
+ "itoa 1.0.10",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+dependencies = [
+ "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 = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+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.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2 0.5.5",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tokio-websockets"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b069bad86dda43d908b4221fe04fe49d2ed8e0a24d319a5c6a8d250e76fe15b"
+dependencies = [
+ "base64 0.21.5",
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "http 1.0.0",
+ "httparse",
+ "rand 0.8.5",
+ "ring",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.1.0",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
+dependencies = [
+ "indexmap 2.1.0",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+dependencies = [
+ "indexmap 2.1.0",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+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.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tray-icon"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad962d06d2bfd9b2ab4f665fc73b175523b834b1466a294520201c5845145f8"
+dependencies = [
+ "cocoa",
+ "core-graphics 0.23.1",
+ "crossbeam-channel",
+ "dirs-next",
+ "libappindicator",
+ "muda",
+ "objc",
+ "once_cell",
+ "png",
+ "serde",
+ "thiserror",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "treediff"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303"
+dependencies = [
+ "serde_json",
+]
+
+[[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.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http 0.2.11",
+ "httparse",
+ "log",
+ "rand 0.8.5",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
+name = "uds_windows"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
+dependencies = [
+ "memoffset 0.9.0",
+ "tempfile",
+ "winapi",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35"
+dependencies = [
+ "base64 0.21.5",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "socks",
+ "url",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "uuid"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
+dependencies = [
+ "getrandom 0.2.11",
+ "sha1_smol",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "value-bag"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74797339c3b98616c009c7c3eb53a0ce41e85c8ec66bd3db96ed132d20cfdee8"
+dependencies = [
+ "value-bag-serde1",
+ "value-bag-sval2",
+]
+
+[[package]]
+name = "value-bag-serde1"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc35703541cbccb5278ef7b589d79439fc808ff0b5867195a3230f9a47421d39"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_fmt",
+]
+
+[[package]]
+name = "value-bag-sval2"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "285b43c29d0b4c0e65aad24561baee67a1b69dc9be9375d4a85138cbf556f7f8"
+dependencies = [
+ "sval",
+ "sval_buffer",
+ "sval_dynamic",
+ "sval_fmt",
+ "sval_json",
+ "sval_ref",
+ "sval_serde",
+]
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version-compare"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "waker-fn"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+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.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
+
+[[package]]
+name = "wasm-streams"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
+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 0.18.1",
+ "glib 0.18.4",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "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 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pkg-config",
+ "soup3-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0ae9c7e420783826cf769d2c06ac9ba462f450eca5893bb8c6c6529a4e5dd33"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows 0.52.0",
+ "windows-core 0.52.0",
+ "windows-implement",
+ "windows-interface",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6ad85fceee6c42fa3d61239eba5a11401bf38407a849ed5ea1b407df08cca72"
+dependencies = [
+ "thiserror",
+ "windows 0.52.0",
+ "windows-core 0.52.0",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
+[[package]]
+name = "which"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix 0.38.30",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "widestring"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
+
+[[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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
+[[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.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af6abc2b9c56bd95887825a1ce56cde49a2a97c07e28db465d541f5098a2656c"
+dependencies = [
+ "cocoa",
+ "objc",
+ "raw-window-handle",
+ "windows-sys 0.52.0",
+ "windows-version",
+]
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
+dependencies = [
+ "windows-core 0.51.1",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core 0.52.0",
+ "windows-implement",
+ "windows-interface",
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
+dependencies = [
+ "windows-core 0.54.0",
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows-service"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9db37ecb5b13762d95468a2fc6009d4b2c62801243223aabd44fca13ad13c8"
+dependencies = [
+ "bitflags 1.3.2",
+ "widestring",
+ "windows-sys 0.45.0",
+]
+
+[[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.4",
+]
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
+]
+
+[[package]]
+name = "windows-version"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+
+[[package]]
+name = "winnow"
+version = "0.5.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "winreg"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "wry"
+version = "0.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ad1bc1d6925e0cde1bd01830b0073cd0448e21357e843b9ede33b6d81c7423"
+dependencies = [
+ "base64 0.21.5",
+ "block",
+ "cfg_aliases",
+ "cocoa",
+ "core-graphics 0.23.1",
+ "crossbeam-channel",
+ "dunce",
+ "gdkx11",
+ "gtk",
+ "html5ever",
+ "http 0.2.11",
+ "javascriptcore-rs",
+ "jni",
+ "kuchikiki",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "objc",
+ "objc_id",
+ "once_cell",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "sha2",
+ "soup3",
+ "tao-macros",
+ "thiserror",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows 0.52.0",
+ "windows-implement",
+ "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 = "x11rb"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
+dependencies = [
+ "gethostname 0.3.0",
+ "nix 0.26.4",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
+dependencies = [
+ "nix 0.26.4",
+]
+
+[[package]]
+name = "xattr"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
+dependencies = [
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "rustix 0.38.30",
+]
+
+[[package]]
+name = "xdg-home"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd"
+dependencies = [
+ "nix 0.26.4",
+ "winapi",
+]
+
+[[package]]
+name = "zbus"
+version = "3.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "byteorder",
+ "derivative",
+ "enumflags2",
+ "event-listener 2.5.3",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix 0.26.4",
+ "once_cell",
+ "ordered-stream",
+ "rand 0.8.5",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "winapi",
+ "xdg-home",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "3.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
+[[package]]
+name = "zip"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
+dependencies = [
+ "aes",
+ "byteorder",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "hmac",
+ "pbkdf2 0.11.0",
+ "sha1",
+ "time",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "zvariant"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c"
+dependencies = [
+ "byteorder",
+ "enumflags2",
+ "libc",
+ "serde",
+ "static_assertions",
+ "zvariant_derive",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
diff --git a/desktop/tauri/src-tauri/Cargo.toml b/desktop/tauri/src-tauri/Cargo.toml
new file mode 100644
index 00000000..6fed0ec3
--- /dev/null
+++ b/desktop/tauri/src-tauri/Cargo.toml
@@ -0,0 +1,75 @@
+[package]
+name = "app"
+version = "0.1.0"
+description = "Portmaster UI"
+authors = ["Safing"]
+license = ""
+repository = ""
+default-run = "app"
+edition = "2021"
+rust-version = "1.60"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[build-dependencies]
+tauri-build = { version = "2.0.0-alpha", features = [] }
+
+[dependencies]
+# Tauri
+tauri = { version = "2.0.0-alpha", features = ["tray-icon", "icon-ico", "icon-png"] }
+tauri-plugin-shell = "2.0.0-alpha"
+tauri-plugin-dialog = "2.0.0-alpha"
+tauri-plugin-clipboard-manager = "2.0.0-alpha"
+tauri-plugin-os = "2.0.0-alpha"
+tauri-plugin-single-instance = "2.0.0-alpha"
+tauri-plugin-cli = "2.0.0-alpha"
+tauri-plugin-notification = "2.0.0-alpha"
+
+# We still need the tauri-cli 1.5 for building
+tauri-cli = "1.5.11"
+
+# General
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+futures-util = { version = "0.3", features = ["sink"] }
+dirs = "1.0"
+rust-ini = "0.20.0"
+dataurl = "0.1.2"
+uuid = "1.6.1"
+lazy_static = "1.4.0"
+tokio = { version = "1.35.0", features = ["macros"] }
+cached = "0.46.1"
+notify-rust = "4.10.0"
+assert_matches = "1.5.0"
+tokio-websockets = { version = "0.5.0", features = ["client", "ring", "rand"] }
+sha = "1.0.3"
+http = "1.0.0"
+url = "2.5.0"
+thiserror = "1.0"
+log = "0.4.21"
+pretty_env_logger = "0.5.0"
+
+# Linux only
+[target.'cfg(target_os = "linux")'.dependencies]
+glib = "0.18.4"
+gtk-sys = "0.18.0"
+glib-sys = "0.18.1"
+gdk-pixbuf = "0.18.3"
+gdk-pixbuf-sys = "0.18.0"
+gio-sys = "0.18.1"
+
+# Windows only
+[target.'cfg(target_os = "windows")'.dependencies]
+windows-service = "0.6.0"
+windows = { version = "0.54.0", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
+
+[dev-dependencies]
+which = "6.0.0"
+gtk = "0.18"
+ctor = "0.2.6"
+
+[features]
+# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
+# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
+# DO NOT REMOVE!!
+custom-protocol = [ "tauri/custom-protocol" ]
diff --git a/desktop/tauri/src-tauri/build.rs b/desktop/tauri/src-tauri/build.rs
new file mode 100644
index 00000000..795b9b7c
--- /dev/null
+++ b/desktop/tauri/src-tauri/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+  tauri_build::build()
+}
diff --git a/desktop/tauri/src-tauri/src/main.rs b/desktop/tauri/src-tauri/src/main.rs
new file mode 100644
index 00000000..2b1def20
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/main.rs
@@ -0,0 +1,204 @@
+// Prevents additional console window on Windows in release, DO NOT REMOVE!!
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
+use tauri_plugin_cli::CliExt;
+
+// Library crates
+mod portapi;
+mod service;
+
+#[cfg(target_os = "linux")]
+mod xdg;
+
+// App modules
+mod portmaster;
+mod traymenu;
+mod window;
+
+use log::{debug, error, info, trace, warn};
+use portmaster::PortmasterExt;
+use traymenu::setup_tray_menu;
+use window::{close_splash_window, create_main_window};
+
+#[macro_use]
+extern crate lazy_static;
+
+#[derive(Clone, serde::Serialize)]
+struct Payload {
+    args: Vec<String>,
+    cwd: String,
+}
+
+struct WsHandler {
+    handle: AppHandle,
+    background: bool,
+
+    is_first_connect: bool,
+}
+
+impl portmaster::Handler for WsHandler {
+    fn on_connect(&mut self, cli: portapi::client::PortAPI) -> () {
+        // we successfully connected to Portmaster. Set is_first_connect to false
+        // so we don't show the splash-screen when we loose connection.
+        self.is_first_connect = false;
+
+        if let Err(err) = close_splash_window(&self.handle) {
+            error!("failed to close splash window: {}", err.to_string());
+        }
+
+        // create the main window now. It's not automatically visible by default.
+        // Rather, the angular application will show the window itself when it finished
+        // bootstrapping.
+        if let Err(err) = create_main_window(&self.handle) {
+            error!("failed to create main window: {}", err.to_string());
+        }
+
+        let handle = self.handle.clone();
+        tauri::async_runtime::spawn(async move {
+            traymenu::tray_handler(cli, handle).await;
+        });
+    }
+
+    fn on_disconnect(&mut self) {
+        // if we're not running in background and this was the first connection attempt
+        // then display the splash-screen.
+        //
+        // Once we had a successful connection the splash-screen will not be shown anymore
+        // since there's already a main window with the angular application.
+        if !self.background && self.is_first_connect {
+            let _ = window::create_splash_window(&self.handle.clone());
+
+            self.is_first_connect = false
+        }
+    }
+}
+
+fn main() {
+    pretty_env_logger::init();
+
+    let app = tauri::Builder::default()
+        // Shell plugin for open_external support
+        .plugin(tauri_plugin_shell::init())
+        // Clipboard support
+        .plugin(tauri_plugin_clipboard_manager::init())
+        // Dialog (Save/Open) support
+        .plugin(tauri_plugin_dialog::init())
+        // OS Version and Architecture support
+        .plugin(tauri_plugin_os::init())
+        // Single instance guard
+        .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
+            let _ = app.emit("single-instance", Payload { args: argv, cwd });
+        }))
+        // Custom CLI arguments
+        .plugin(tauri_plugin_cli::init())
+        // Notification support
+        .plugin(tauri_plugin_notification::init())
+        // Our Portmaster Plugin that handles communication between tauri and our angular app.
+        .plugin(portmaster::init())
+        // Setup the app an any listeners
+        .setup(|app| {
+            setup_tray_menu(app)?;
+
+            // Setup the single-instance event listener that will create/focus the main window
+            // or the splash-screen.
+            let handle = app.handle().clone();
+            app.listen_global("single-instance", move |_event| {
+                let _ = window::open_window(&handle);
+            });
+
+            // Handle cli flags:
+            //
+            let mut background = false;
+            match app.cli().matches() {
+                Ok(matches) => {
+                    debug!("cli matches={:?}", matches);
+
+                    if let Some(bg_flag) = matches.args.get("background") {
+                        match bg_flag.value.as_bool() {
+                            Some(value) => {
+                                background = value;
+                                app.portmaster().set_show_after_bootstrap(!background);
+                            }
+                            None => {}
+                        }
+                    }
+
+                    if let Some(nf_flag) = matches.args.get("with-notifications") {
+                        match nf_flag.value.as_bool() {
+                            Some(v) => {
+                                app.portmaster().with_notification_support(v);
+                            }
+                            None => {}
+                        }
+                    }
+
+                    if let Some(pf_flag) = matches.args.get("with-prompts") {
+                        match pf_flag.value.as_bool() {
+                            Some(v) => {
+                                app.portmaster().with_connection_prompts(v);
+                            }
+                            None => {}
+                        }
+                    }
+                }
+                Err(err) => {
+                    error!("failed to parse cli arguments: {}", err.to_string());
+                }
+            };
+
+            // prepare a custom portmaster plugin handler that will show the splash-screen
+            // (if not in --background) and launch the tray-icon handler.
+            let handler = WsHandler {
+                handle: app.handle().clone(),
+                background,
+                is_first_connect: true,
+            };
+
+            // register the custom handler
+            app.portmaster().register_handler(handler);
+
+            Ok(())
+        })
+        .any_thread()
+        .build(tauri::generate_context!())
+        .expect("error while running tauri application");
+
+    app.run(|handle, e| match e {
+        RunEvent::WindowEvent { label, event, .. } => {
+            if label != "main" {
+                // We only have one window at most so any other label is unexpected
+                return;
+            }
+
+            // Do not let the user close the window, instead send an event to the main
+            // window so we can show the "will not stop portmaster" dialog and let the window
+            // close itself using
+            //
+            //    window.__TAURI__.window.getCurrent().close()
+            //
+            // Note: the above javascript does NOT trigger the CloseRequested event so
+            // there's no need to handle that case here.
+            //
+            match event {
+                WindowEvent::CloseRequested { api, .. } => {
+                    debug!(
+                        "window (label={}) close request received, forwarding to user-interface.",
+                        label
+                    );
+
+                    api.prevent_close();
+                    if let Some(window) = handle.get_window(label.as_str()) {
+                        let _ = window.emit("exit-requested", "");
+                    }
+                }
+                _ => {}
+            }
+        }
+
+        RunEvent::ExitRequested { api, .. } => {
+            api.prevent_exit();
+        }
+        _ => {}
+    });
+}
diff --git a/desktop/tauri/src-tauri/src/portapi/client.rs b/desktop/tauri/src-tauri/src/portapi/client.rs
new file mode 100644
index 00000000..b3b00d09
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/client.rs
@@ -0,0 +1,191 @@
+use futures_util::{SinkExt, StreamExt};
+use http::Uri;
+use log::{debug, error, warn};
+use std::collections::HashMap;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use tokio::sync::mpsc::{channel, Receiver, Sender};
+use tokio::sync::RwLock;
+use tokio_websockets::{ClientBuilder, Error};
+
+use super::message::*;
+use super::types::*;
+
+/// An internal representation of a Command that
+/// contains the PortAPI message as well as a response
+/// channel that will receive all responses sent from the
+/// server.
+///
+/// Users should normally not need to use the Command struct
+/// directly since `PortAPI` already abstracts the creation of
+/// mpsc channels.
+struct Command {
+    msg: Message,
+    response: Sender<Response>,
+}
+
+/// The client implementation for PortAPI.
+#[derive(Clone)]
+pub struct PortAPI {
+    dispatch: Sender<Command>,
+}
+
+/// The map type used to store message subscribers.
+type SubscriberMap = RwLock<HashMap<usize, Sender<Response>>>;
+
+/// Connect to PortAPI at the specified URI.
+///
+/// This method will launch a new async thread on the `tauri::async_runtime`
+/// that will handle message to transmit and also multiplex server responses
+/// to the appropriate subscriber.
+pub async fn connect(uri: &str) -> Result<PortAPI, Error> {
+    let parsed = match uri.parse::<Uri>() {
+        Ok(u) => u,
+        Err(_e) => {
+            return Err(Error::NoUriConfigured); // TODO(ppacher): fix the return error type.
+        }
+    };
+
+    let (mut client, _) = ClientBuilder::from_uri(parsed).connect().await?;
+    let (tx, mut dispatch) = channel::<Command>(64);
+
+    tauri::async_runtime::spawn(async move {
+        let subscribers: SubscriberMap = RwLock::new(HashMap::new());
+        let next_id = AtomicUsize::new(0);
+
+        loop {
+            tokio::select! {
+                msg = client.next() => {
+                    let msg = match msg {
+                        Some(msg) => msg,
+                        None => {
+                            warn!("websocket connection lost");
+
+                            dispatch.close();
+                            return;
+                        }
+                    };
+
+                    match msg {
+                        Err(err) => {
+                            error!("failed to receive frame from websocket: {}", err);
+
+                            dispatch.close();
+                            return;
+                        },
+                        Ok(msg) => {
+                            let text = unsafe {
+                                std::str::from_utf8_unchecked(msg.as_payload())
+                            };
+
+                            match text.parse::<Message>() {
+                                Ok(msg) => {
+                                    let id = msg.id;
+                                    let map = subscribers
+                                        .read()
+                                        .await;
+
+                                    if let Some(sub) = map.get(&id) {
+                                        let res: Result<Response, MessageError> = msg.try_into();
+                                        match res {
+                                            Ok(response) => {
+                                                if let Err(err) = sub.send(response).await {
+                                                    // The receiver side has been closed already,
+                                                    // drop the read lock and remove the subscriber
+                                                    // from our hashmap
+                                                    drop(map);
+
+                                                    subscribers
+                                                        .write()
+                                                        .await
+                                                        .remove(&id);
+
+                                                    debug!("subscriber for command {} closed read side: {}", id, err);
+                                                }
+                                            },
+                                            Err(err) => {
+                                                error!("invalid command: {}", err);
+                                            }
+                                        }
+                                    }
+                                },
+                                Err(err) => {
+                                    error!("failed to deserialize message: {}", err)
+                                }
+                            }
+                        }
+                    }
+
+                },
+
+                Some(mut cmd) = dispatch.recv() => {
+                    let id = next_id.fetch_add(1, Ordering::Relaxed);
+                    cmd.msg.id = id;
+                    let blob: String = cmd.msg.into();
+
+                    debug!("Sending websocket frame: {}", blob);
+
+                    match client.send(tokio_websockets::Message::text(blob)).await {
+                        Ok(_) => {
+                            subscribers
+                                .write()
+                                .await
+                                .insert(id, cmd.response);
+                        },
+                        Err(err) => {
+                            error!("failed to dispatch command: {}", err);
+
+                            // TODO(ppacher): we should send some error to cmd.response here.
+                            // Otherwise, the sender of cmd might get stuck waiting for responses
+                            // if they don't check for PortAPI.is_closed().
+
+                            return
+                        }
+                    }
+                }
+            }
+        }
+    });
+
+    Ok(PortAPI { dispatch: tx })
+}
+
+impl PortAPI {
+    /// `request` sends a PortAPI `portapi::types::Request` to the server and returns a mpsc receiver channel
+    /// where all server responses are forwarded.
+    ///
+    /// If the caller does not intend to read any responses the returned receiver may be closed or
+    /// dropped. As soon as the async-thread launched in `connect` detects a closed receiver it is remove
+    /// from the subscription map.
+    ///
+    /// The default buffer size for the channel is 64. Use `request_with_buffer_size` to specify a dedicated buffer size.
+    pub async fn request(
+        &self,
+        r: Request,
+    ) -> std::result::Result<Receiver<Response>, MessageError> {
+        self.request_with_buffer_size(r, 64).await
+    }
+
+    // Like `request` but supports explicitly specifying a channel buffer size.
+    pub async fn request_with_buffer_size(
+        &self,
+        r: Request,
+        buffer: usize,
+    ) -> std::result::Result<Receiver<Response>, MessageError> {
+        let (tx, rx) = channel(buffer);
+
+        let msg: Message = r.try_into()?;
+
+        let _ = self.dispatch.send(Command { response: tx, msg }).await;
+
+        Ok(rx)
+    }
+
+    /// Reports whether or not the websocket connection to the Portmaster Database API has been closed
+    /// due to errors.
+    ///
+    /// Users are expected to check this field on a regular interval to detect any issues and perform
+    /// a clean re-connect by calling `connect` again.
+    pub fn is_closed(&self) -> bool {
+        self.dispatch.is_closed()
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/portapi/message.rs b/desktop/tauri/src-tauri/src/portapi/message.rs
new file mode 100644
index 00000000..46eb7c77
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/message.rs
@@ -0,0 +1,258 @@
+use thiserror::Error;
+
+/// MessageError describes any error that is encountered when parsing
+/// PortAPI messages or when converting between the Request/Response types.
+#[derive(Debug, Error)]
+pub enum MessageError {
+    #[error("missing command id")]
+    MissingID,
+
+    #[error("invalid command id")]
+    InvalidID,
+
+    #[error("missing command")]
+    MissingCommand,
+
+    #[error("missing key")]
+    MissingKey,
+
+    #[error("missing payload")]
+    MissingPayload,
+
+    #[error("unknown or unsupported command: {0}")]
+    UnknownCommand(String),
+
+    #[error(transparent)]
+    InvalidPayload(#[from] serde_json::Error),
+}
+
+
+/// Payload defines the payload type and content of a PortAPI message.
+/// 
+/// For the time being, only JSON payloads (indicated by a prefixed 'J' of the payload content)
+/// is directly supported in `Payload::parse()`.
+/// 
+/// For other payload types (like CBOR, BSON, ...) it's the user responsibility to figure out
+/// appropriate decoding from the `Payload::UNKNOWN` variant.
+#[derive(PartialEq, Debug, Clone)]
+pub enum Payload {
+    JSON(String),
+    UNKNOWN(String),
+}
+
+/// ParseError is returned from `Payload::parse()`.
+#[derive(Debug, Error)]
+pub enum ParseError {
+    #[error(transparent)]
+    JSON(#[from] serde_json::Error),
+
+    #[error("unknown error while parsing")]
+    UNKNOWN
+}
+
+
+impl Payload {
+    /// Parse the payload into T.
+    /// 
+    /// Only JSON parsing is supported for now. See [Payload] for more information.
+    pub fn parse<'a, T>(self: &'a Self) -> std::result::Result<T, ParseError> 
+    where
+        T: serde::de::Deserialize<'a> {
+
+        match self {
+            Payload::JSON(blob) => Ok(serde_json::from_str::<T>(blob.as_str())?),
+            Payload::UNKNOWN(_) => Err(ParseError::UNKNOWN),
+        }
+    }
+}
+
+/// Supports creating a Payload instance from a String.
+/// 
+/// See [Payload] for more information.
+impl std::convert::From<String> for Payload {
+    fn from(value: String) -> Payload {
+        let mut chars = value.chars();
+        let first = chars.next();
+        let rest = chars.as_str().to_string();
+
+        match first {
+            Some(c) => match c {
+                'J' => Payload::JSON(rest),
+                _ => Payload::UNKNOWN(value),
+            },
+            None => Payload::UNKNOWN("".to_string())
+        }
+    }
+}
+
+/// Display implementation for Payload that just displays the raw payload.
+impl std::fmt::Display for Payload {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Payload::JSON(payload) => {
+                write!(f, "J{}", payload)
+            },
+            Payload::UNKNOWN(payload) => {
+                write!(f, "{}", payload)
+            }
+        }
+    }
+}
+
+/// Message is an internal representation of a PortAPI message.
+/// Users should more likely use `portapi::types::Request` and `portapi::types::Response` 
+/// instead of directly using `Message`.
+/// 
+/// The struct is still public since it might be useful for debugging or to implement new
+/// commands not yet supported by the `portapi::types` crate.
+#[derive(PartialEq, Debug, Clone)]
+pub struct Message {
+    pub id: usize,
+    pub cmd: String,
+    pub key: Option<String>,
+    pub payload: Option<Payload>,
+}
+
+/// Implementation to marshal a PortAPI message into it's wire-format representation
+/// (which is a string).
+/// 
+/// Note that this conversion does not check for invalid messages!
+impl std::convert::From<Message> for String {
+    fn from(value: Message) -> Self {
+        let mut result = "".to_owned();
+
+        result.push_str(value.id.to_string().as_str());
+        result.push_str("|");
+        result.push_str(&value.cmd);
+
+        if let Some(key) = value.key {
+            result.push_str("|");
+            result.push_str(key.as_str());
+        }
+
+        if let Some(payload) = value.payload {
+            result.push_str("|");
+            result.push_str(payload.to_string().as_str())
+        }
+
+        result
+    }
+}
+
+/// An implementation for `String::parse()` to convert a wire-format representation
+/// of a PortAPI message to a Message instance.
+/// 
+/// Any errors returned from `String::parse()` will be of type `MessageError`
+impl std::str::FromStr for Message {
+    type Err = MessageError;
+
+    fn from_str(line: &str) -> Result<Self, Self::Err> {
+        let parts = line.split("|").collect::<Vec<&str>>();
+
+        let id = match parts.get(0) {
+            Some(s) => match (*s).parse::<usize>() {
+                Ok(id) => Ok(id),
+                Err(_) => Err(MessageError::InvalidID),
+            },
+            None => Err(MessageError::MissingID),
+        }?;
+
+        let cmd = match parts.get(1) {
+            Some(s) => Ok(*s),
+            None => Err(MessageError::MissingCommand),
+        }?
+        .to_string();
+
+        let key = parts.get(2)
+            .and_then(|key| Some(key.to_string()));
+
+        let payload : Option<Payload> = parts.get(3)
+            .and_then(|p| Some(p.to_string().into()));
+
+        return Ok(Message {
+            id,
+            cmd,
+            key,
+            payload: payload
+        });
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde::Deserialize;
+
+    #[derive(Debug, PartialEq, Deserialize)]
+    struct Test {
+        a: i64,
+        s: String,
+    }
+
+    #[test]
+    fn payload_to_string() {
+        let p = Payload::JSON("{}".to_string());
+        assert_eq!(p.to_string(), "J{}");
+
+        let p = Payload::UNKNOWN("some unknown content".to_string());
+        assert_eq!(p.to_string(), "some unknown content");
+    }
+
+    #[test]
+    fn payload_from_string() {
+        let p: Payload = "J{}".to_string().into();
+        assert_eq!(p, Payload::JSON("{}".to_string()));
+
+        let p: Payload = "some unknown content".to_string().into();
+        assert_eq!(p, Payload::UNKNOWN("some unknown content".to_string()));
+    }
+
+    #[test]
+    fn payload_parse() {
+        let p: Payload = "J{\"a\": 100, \"s\": \"string\"}".to_string().into();
+
+        let t: Test = p.parse()
+            .expect("Expected payload parsing to work");
+
+        assert_eq!(t, Test{
+            a: 100,
+            s: "string".to_string(),
+        });
+    }
+
+    #[test]
+    fn parse_message() {
+        let m = "10|insert|some:key|J{}".parse::<Message>()
+            .expect("Expected message to parse");
+
+        assert_eq!(m, Message{
+            id: 10,
+            cmd: "insert".to_string(),
+            key: Some("some:key".to_string()),
+            payload: Some(Payload::JSON("{}".to_string())),
+        });
+
+        let m = "1|done".parse::<Message>()
+            .expect("Expected message to parse");
+
+        assert_eq!(m, Message{
+            id: 1,
+            cmd: "done".to_string(),
+            key: None,
+            payload: None
+        });
+
+        let m = "".parse::<Message>()
+            .expect_err("Expected parsing to fail");
+        if let MessageError::InvalidID = m {} else {
+            panic!("unexpected error value: {}", m)
+        }
+
+        let m = "1".parse::<Message>()
+            .expect_err("Expected parsing to fail");
+
+        if let MessageError::MissingCommand = m {} else {
+            panic!("unexpected error value: {}", m)
+        }
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/portapi/mod.rs b/desktop/tauri/src-tauri/src/portapi/mod.rs
new file mode 100644
index 00000000..67fd2710
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/mod.rs
@@ -0,0 +1,4 @@
+pub mod client;
+pub mod message;
+pub mod types;
+pub mod models;
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/src/portapi/models/config.rs b/desktop/tauri/src-tauri/src/portapi/models/config.rs
new file mode 100644
index 00000000..e29474a8
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/models/config.rs
@@ -0,0 +1,18 @@
+use serde::*;
+use super::super::message::Payload;
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct BooleanValue {
+    #[serde(rename = "Value")]
+    pub value: Option<bool>,
+}
+
+impl TryInto<Payload> for BooleanValue {
+    type Error = serde_json::Error;
+
+    fn try_into(self) -> Result<Payload, Self::Error> {
+        let str = serde_json::to_string(&self)?;
+
+        Ok(Payload::JSON(str))
+    }
+}
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/src/portapi/models/mod.rs b/desktop/tauri/src-tauri/src/portapi/models/mod.rs
new file mode 100644
index 00000000..91336dd0
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/models/mod.rs
@@ -0,0 +1,4 @@
+pub mod config;
+pub mod spn;
+pub mod notification;
+pub mod subsystem;
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/src/portapi/models/notification.rs b/desktop/tauri/src-tauri/src/portapi/models/notification.rs
new file mode 100644
index 00000000..51f4ece4
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/models/notification.rs
@@ -0,0 +1,70 @@
+use serde::*;
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+pub struct Notification {
+    #[serde(rename = "EventID")]
+    pub event_id: String,
+
+    #[serde(rename = "GUID")]
+    pub guid: String,
+
+    #[serde(rename = "Type")]
+    pub notification_type: NotificationType,
+
+    #[serde(rename = "Message")]
+    pub message: String,
+
+    #[serde(rename = "Title")]
+    pub title: String,
+    #[serde(rename = "Category")]
+    pub category: String,
+
+    #[serde(rename = "EventData")]
+    pub data: serde_json::Value,
+
+    #[serde(rename = "Expires")]
+    pub expires: u64,
+
+    #[serde(rename = "State")]
+    pub state: String,
+
+    #[serde(rename = "AvailableActions")]
+    pub actions: Vec<Action>,
+
+    #[serde(rename = "SelectedActionID")]
+    pub selected_action_id: String,
+
+    #[serde(rename = "ShowOnSystem")]
+    pub show_on_system: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+pub struct Action {
+    #[serde(rename = "ID")]
+    pub id: String,
+
+    #[serde(rename = "Text")]
+    pub text: String,
+
+    #[serde(rename = "Type")]
+    pub action_type: String,
+
+    #[serde(rename = "Payload")]
+    pub payload: serde_json::Value,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+pub struct NotificationType(i32);
+
+#[allow(dead_code)]
+pub const INFO: NotificationType = NotificationType(0);
+
+#[allow(dead_code)]
+pub const WARN: NotificationType = NotificationType(1);
+
+#[allow(dead_code)]
+pub const PROMPT: NotificationType = NotificationType(2);
+
+#[allow(dead_code)]
+pub const ERROR: NotificationType = NotificationType(3);
+
diff --git a/desktop/tauri/src-tauri/src/portapi/models/spn.rs b/desktop/tauri/src-tauri/src/portapi/models/spn.rs
new file mode 100644
index 00000000..549c2e27
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/models/spn.rs
@@ -0,0 +1,8 @@
+use serde::*;
+
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct SPNStatus {
+    #[serde(rename = "Status")]
+    pub status: String,
+}
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/src/portapi/models/subsystem.rs b/desktop/tauri/src-tauri/src/portapi/models/subsystem.rs
new file mode 100644
index 00000000..c8b0ea27
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/models/subsystem.rs
@@ -0,0 +1,45 @@
+#![allow(dead_code)]
+use serde::*;
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct ModuleStatus {
+    #[serde(rename = "Name")]
+    pub name: String,
+
+    #[serde(rename = "Enabled")]
+    pub enabled: bool,
+
+    #[serde(rename = "Status")]
+    pub status: u8,
+
+    #[serde(rename = "FailureStatus")]
+    pub failure_status: u8,
+
+    #[serde(rename = "FailureID")]
+    pub failure_id: String,
+
+    #[serde(rename = "FailureMsg")]
+    pub failure_msg: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct Subsystem {
+    #[serde(rename = "ID")]
+    pub id: String,
+
+    #[serde(rename = "Name")]
+    pub name: String,
+
+    #[serde(rename = "Description")]
+    pub description: String,
+
+    #[serde(rename = "Modules")]
+    pub module_status: Vec<ModuleStatus>,
+
+    #[serde(rename = "FailureStatus")]
+    pub failure_status: u8,
+}
+pub const FAILURE_NONE: u8 = 0;
+pub const FAILURE_HINT: u8 = 1;
+pub const FAILURE_WARNING: u8 = 2;
+pub const FAILURE_ERROR: u8 = 3;
diff --git a/desktop/tauri/src-tauri/src/portapi/types.rs b/desktop/tauri/src-tauri/src/portapi/types.rs
new file mode 100644
index 00000000..632f24f4
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portapi/types.rs
@@ -0,0 +1,199 @@
+
+use super::message::*;
+
+/// Request is a strongly typed request message
+/// that can be converted to a `portapi::message::Message`
+/// object for further use by the client (`portapi::client::PortAPI`).
+#[derive(PartialEq, Debug)]
+pub enum Request {
+    Get(String),
+    Query(String),
+    Subscribe(String),
+    QuerySubscribe(String),
+    Create(String, Payload),
+    Update(String, Payload),
+    Insert(String, Payload),
+    Delete(String),
+    Cancel,
+}
+
+/// Implementation to convert a internal `portapi::message::Message` to a valid
+/// `Request` variant.
+/// 
+/// Any error returned will be of type `portapi::message::MessageError`.
+impl std::convert::TryFrom<Message> for Request {
+    type Error = MessageError;
+
+    fn try_from(value: Message) -> Result<Self, Self::Error> {
+        match value.cmd.as_str() {
+            "get" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                Ok(Request::Get(key))
+            },
+            "query" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                Ok(Request::Query(key))
+            },
+            "sub" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                Ok(Request::Subscribe(key))
+            },
+            "qsub" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                Ok(Request::QuerySubscribe(key))
+            },
+            "create" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+                Ok(Request::Create(key, payload))
+            },
+            "update" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+                Ok(Request::Update(key, payload))
+            },
+            "insert" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+                Ok(Request::Insert(key, payload))
+            },
+            "delete" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                Ok(Request::Delete(key))
+            },
+            "cancel" => {
+                Ok(Request::Cancel)
+            },
+            cmd => {
+                Err(MessageError::UnknownCommand(cmd.to_string()))
+            }
+        }     
+    }
+}
+
+/// An implementation to try to convert a `Request` variant into a valid 
+/// `portapi::message::Message` struct.
+/// 
+/// While this implementation does not yet return any errors, it's expected that
+/// additional validation will be added in the future so users should already expect
+/// to receive `portapi::message::MessageError`s.
+impl std::convert::TryFrom<Request> for Message {
+    type Error = MessageError;
+
+    fn try_from(value: Request) -> Result<Self, Self::Error> {
+        match value {
+            Request::Get(key) => Ok(Message { id: 0, cmd: "get".to_string(), key: Some(key), payload: None }),
+            Request::Query(key) => Ok(Message { id: 0, cmd: "query".to_string(), key: Some(key), payload: None }),
+            Request::Subscribe(key) => Ok(Message { id: 0, cmd: "sub".to_string(), key: Some(key), payload: None }),
+            Request::QuerySubscribe(key) => Ok(Message { id: 0, cmd: "qsub".to_string(), key: Some(key), payload: None }),
+            Request::Create(key, value) => Ok(Message{ id: 0, cmd: "create".to_string(), key: Some(key), payload: Some(value)}),
+            Request::Update(key, value) => Ok(Message{ id: 0, cmd: "update".to_string(), key: Some(key), payload: Some(value)}),
+            Request::Insert(key, value) => Ok(Message{ id: 0, cmd: "insert".to_string(), key: Some(key), payload: Some(value)}),
+            Request::Delete(key) => Ok(Message { id: 0, cmd: "delete".to_string(), key: Some(key), payload: None }),
+            Request::Cancel => Ok(Message { id: 0, cmd: "cancel".to_string(), key: None, payload: None }),
+        }
+    }
+}
+
+
+/// Response is strongly types PortAPI response message.
+/// that can be converted to a `portapi::message::Message`
+/// object for further use by the client (`portapi::client::PortAPI`).
+#[derive(PartialEq, Debug)]
+pub enum Response {
+    Ok(String, Payload),
+    Update(String, Payload),
+    New(String, Payload),
+    Delete(String),
+    Success,
+    Error(String),
+    Warning(String),
+    Done
+}
+
+/// Implementation to convert a internal `portapi::message::Message` to a valid
+/// `Response` variant.
+/// 
+/// Any error returned will be of type `portapi::message::MessageError`.
+impl std::convert::TryFrom<Message> for Response {
+    type Error = MessageError;
+
+    fn try_from(value: Message) -> Result<Self, MessageError> {
+        match value.cmd.as_str() {
+            "ok" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+
+                Ok(Response::Ok(key, payload))
+            },
+            "upd" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+
+                Ok(Response::Update(key, payload))
+            },
+            "new" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+                let payload = value.payload.ok_or(MessageError::MissingPayload)?;
+
+                Ok(Response::New(key, payload))
+            },
+            "del" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+
+                Ok(Response::Delete(key))
+            },
+            "success" => {
+                Ok(Response::Success)
+            },
+            "error" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+
+                Ok(Response::Error(key))
+            },
+            "warning" => {
+                let key = value.key.ok_or(MessageError::MissingKey)?;
+
+                Ok(Response::Warning(key))
+            },
+            "done" => {
+                Ok(Response::Done)
+            },
+            cmd => Err(MessageError::UnknownCommand(cmd.to_string()))
+        }
+    }
+}
+
+/// An implementation to try to convert a `Response` variant into a valid 
+/// `portapi::message::Message` struct.
+/// 
+/// While this implementation does not yet return any errors, it's expected that
+/// additional validation will be added in the future so users should already expect
+/// to receive `portapi::message::MessageError`s.
+impl std::convert::TryFrom<Response> for Message {
+    type Error = MessageError;
+
+    fn try_from(value: Response) -> Result<Self, Self::Error> {
+        match value {
+            Response::Ok(key, payload) => Ok(Message{id: 0, cmd: "ok".to_string(), key: Some(key), payload: Some(payload)}),
+            Response::Update(key, payload) => Ok(Message{id: 0, cmd: "upd".to_string(), key: Some(key), payload: Some(payload)}),
+            Response::New(key, payload) => Ok(Message{id: 0, cmd: "new".to_string(), key: Some(key), payload: Some(payload)}),
+            Response::Delete(key ) => Ok(Message{id: 0, cmd: "del".to_string(), key: Some(key), payload: None}),
+            Response::Success => Ok(Message{id: 0, cmd: "success".to_string(), key: None, payload: None}),
+            Response::Warning(key) => Ok(Message{id: 0, cmd: "warning".to_string(), key: Some(key), payload: None}),
+            Response::Error(key) => Ok(Message{id: 0, cmd: "error".to_string(), key: Some(key), payload: None}),
+            Response::Done => Ok(Message{id: 0, cmd: "done".to_string(), key: None, payload: None}),
+        }
+    }
+}
+
+
+#[derive(serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "UPPERCASE")]
+pub struct Record {
+    pub created: u64,
+    pub deleted: u64,
+    pub expires: u64,
+    pub modified: u64,
+    pub key: String,
+}
\ No newline at end of file
diff --git a/desktop/tauri/src-tauri/src/portmaster/commands.rs b/desktop/tauri/src-tauri/src/portmaster/commands.rs
new file mode 100644
index 00000000..dfa2b222
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portmaster/commands.rs
@@ -0,0 +1,182 @@
+use super::PortmasterPlugin;
+use crate::service::get_service_manager;
+use crate::service::ServiceManager;
+use log::debug;
+use std::sync::atomic::Ordering;
+use tauri::{Manager, Runtime, State, Window};
+
+pub type Result = std::result::Result<String, String>;
+
+#[derive(Clone, serde::Serialize, serde::Deserialize)]
+pub struct Error {
+    pub error: String,
+}
+
+#[tauri::command]
+pub fn should_show<R: Runtime>(
+    _window: Window<R>,
+    portmaster: State<'_, PortmasterPlugin<R>>,
+) -> Result {
+    if portmaster.get_show_after_bootstrap() {
+        debug!("[tauri:rpc:should_show] application should show after bootstrap");
+
+        Ok("show".to_string())
+    } else {
+        debug!("[tauri:rpc:should_show] application should hide after bootstrap");
+
+        Ok("hide".to_string())
+    }
+}
+
+#[tauri::command]
+pub fn should_handle_prompts<R: Runtime>(
+    _window: Window<R>,
+    portmaster: State<'_, PortmasterPlugin<R>>,
+) -> Result {
+    if portmaster.handle_prompts.load(Ordering::Relaxed) {
+        Ok("true".to_string())
+    } else {
+        Ok("false".to_string())
+    }
+}
+
+#[tauri::command]
+pub fn get_state<R: Runtime>(
+    _window: Window<R>,
+    portmaster: State<'_, PortmasterPlugin<R>>,
+    key: String,
+) -> Result {
+    let value = portmaster.get_state(key);
+
+    if let Some(value) = value {
+        Ok(value)
+    } else {
+        Ok("".to_string())
+    }
+}
+
+#[tauri::command]
+pub fn set_state<R: Runtime>(
+    _window: Window<R>,
+    portmaster: State<'_, PortmasterPlugin<R>>,
+    key: String,
+    value: String,
+) -> Result {
+    portmaster.set_state(key, value);
+
+    Ok("".to_string())
+}
+
+#[cfg(target_os = "linux")]
+#[tauri::command]
+pub fn get_app_info<R: Runtime>(
+    window: Window<R>,
+    response_id: String,
+    matching_path: String,
+    exec_path: String,
+    pid: i64,
+    cmdline: String,
+) -> Result {
+    let mut id = response_id;
+
+    let info = crate::xdg::ProcessInfo {
+        cmdline,
+        exec_path,
+        pid,
+        matching_path,
+    };
+
+    if id == "" {
+        id = uuid::Uuid::new_v4().to_string()
+    }
+    let cloned = id.clone();
+
+    std::thread::spawn(move || match crate::xdg::get_app_info(info) {
+        Ok(info) => window.emit(&id, info),
+        Err(err) => window.emit(
+            &id,
+            Error {
+                error: err.to_string(),
+            },
+        ),
+    });
+
+    Ok(cloned)
+}
+
+#[cfg(target_os = "windows")]
+#[tauri::command]
+pub fn get_app_info<R: Runtime>(
+    window: Window<R>,
+    response_id: String,
+    _matching_path: String,
+    _exec_path: String,
+    _pid: i64,
+    _cmdline: String,
+) -> Result {
+    let mut id = response_id;
+
+    if id == "" {
+        id = uuid::Uuid::new_v4().to_string()
+    }
+    let cloned = id.clone();
+
+    std::thread::spawn(move || {
+        let _ = window.emit(
+            &id,
+            Error {
+                error: "Unsupported OS".to_string(),
+            },
+        );
+    });
+
+    Ok(cloned)
+}
+
+#[tauri::command]
+pub fn get_service_manager_status<R: Runtime>(window: Window<R>, response_id: String) -> Result {
+    let mut id = response_id;
+
+    if id == "" {
+        id = uuid::Uuid::new_v4().to_string();
+    }
+    let cloned = id.clone();
+
+    std::thread::spawn(move || {
+        let result = match get_service_manager() {
+            Ok(sm) => sm.status().map_err(|err| err.to_string()),
+            Err(err) => Err(err.to_string()),
+        };
+
+        match result {
+            Ok(result) => window.emit(&id, &result),
+            Err(err) => window.emit(&id, Error { error: err }),
+        }
+    });
+
+    Ok(cloned)
+}
+
+#[tauri::command]
+pub fn start_service<R: Runtime>(window: Window<R>, response_id: String) -> Result {
+    let mut id = response_id;
+
+    if id == "" {
+        id = uuid::Uuid::new_v4().to_string();
+    }
+    let cloned = id.clone();
+
+    std::thread::spawn(move || {
+        let result = match get_service_manager() {
+            Ok(sm) => sm.start().map_err(|err| err.to_string()),
+            Err(err) => Err(err.to_string()),
+        };
+
+        match result {
+            Ok(result) => window.emit(&id, &result),
+            Err(err) => window.emit(&id, Error { error: err }),
+        }
+    });
+
+    Ok(cloned)
+}
diff --git a/desktop/tauri/src-tauri/src/portmaster/mod.rs b/desktop/tauri/src-tauri/src/portmaster/mod.rs
new file mode 100644
index 00000000..a5844949
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portmaster/mod.rs
@@ -0,0 +1,294 @@
+/// This module contains a custom tauri plugin that handles all communication
+/// with the angular app loaded from the portmaster api.
+///
+/// Using a custom-plugin for this has the advantage that all code that has
+/// access to a tauri::Window or a tauri::AppHandle can get access to the
+/// portmaster plugin using the Runtime/Manager extension by just calling
+/// window.portmaster() or app_handle.portmaster().
+///
+/// Any portmaster related features (like changing a portmaster setting) should
+/// live in this module.
+///
+/// Code that handles windows should NOT live here but should rather be placed
+/// in the crate root.
+// The commands module contains tauri commands that are available to Javascript
+// using the invoke() and our custom invokeAsync() command.
+mod commands;
+
+// The websocket module spawns an async function on tauri's runtime that manages
+// a persistent connection to the Portmaster websocket API and updates the tauri Portmaster
+// Plugin instance.
+mod websocket;
+
+// The notification module manages system notifications from portmaster.
+mod notifications;
+
+use crate::portapi::{
+    client::PortAPI, message::Payload, models::config::BooleanValue, types::Request,
+};
+use std::{
+    collections::HashMap,
+    sync::atomic::{AtomicBool, Ordering},
+};
+
+use log::debug;
+use serde;
+use std::sync::Mutex;
+use tauri::{
+    plugin::{Builder, TauriPlugin},
+    AppHandle, Manager, Runtime,
+};
+
+pub trait Handler {
+    fn on_connect(&mut self, cli: PortAPI) -> ();
+    fn on_disconnect(&mut self);
+}
+
+pub struct PortmasterPlugin<R: Runtime> {
+    #[allow(dead_code)]
+    app: AppHandle<R>,
+
+    // state allows the angular application to store arbitrary values in the
+    // tauri application memory using the get_state and set_state
+    // tauri::commands.
+    state: Mutex<HashMap<String, String>>,
+
+    // an atomic boolean that indicates if we're currently connected to
+    // portmaster or not.
+    is_reachable: AtomicBool,
+
+    // holds the portapi client if any.
+    api: Mutex<Option<PortAPI>>,
+
+    // a vector of handlers that should be invoked on connect and disconnect of
+    // the portmaster API.
+    handlers: Mutex<Vec<Box<dyn Handler + Send>>>,
+
+    // whether or not we should handle notifications here.
+    handle_notifications: AtomicBool,
+
+    // whether or not we should handle prompts.
+    handle_prompts: AtomicBool,
+
+    // whether or not the angular application should call window.show after it
+    // finished bootstrapping.
+    should_show_after_bootstrap: AtomicBool,
+}
+
+impl<R: Runtime> PortmasterPlugin<R> {
+    /// Returns a state stored in the portmaster plugin.
+    pub fn get_state(&self, key: String) -> Option<String> {
+        let map = self.state.lock();
+
+        if let Ok(map) = map {
+            match map.get(&key) {
+                Some(value) => Some(value.clone()),
+                None => None,
+            }
+        } else {
+            None
+        }
+    }
+
+    /// Adds a new state to the portmaster plugin.
+    pub fn set_state(&self, key: String, value: String) {
+        let map = self.state.lock();
+
+        if let Ok(mut map) = map {
+            map.insert(key, value);
+        }
+    }
+
+    /// Reports wheter or not we're currently connected to the Portmaster API.
+    pub fn is_reachable(&self) -> bool {
+        self.is_reachable.load(Ordering::Relaxed)
+    }
+
+    /// Registers a new connection handler that is called on connect
+    /// and disconnect of the Portmaster websocket API.
+    pub fn register_handler(&self, mut handler: impl Handler + Send + 'static) {
+        // register_handler can only be invoked after the plugin setup
+        // completed. in this case, the websocket thread is already spawned and
+        // we might already be connected or know that the connection failed.
+        // Call the respective handler method immediately now.
+        if let Some(api) = self.get_api() {
+            handler.on_connect(api);
+        } else {
+            handler.on_disconnect();
+        }
+
+        if let Ok(mut handlers) = self.handlers.lock() {
+            handlers.push(Box::new(handler));
+        }
+    }
+
+    /// Returns the current portapi client.
+    pub fn get_api(&self) -> Option<PortAPI> {
+        if let Ok(mut api) = self.api.lock() {
+            match &mut *api {
+                Some(api) => Some(api.clone()),
+                None => None,
+            }
+        } else {
+            None
+        }
+    }
+
+    /// Feature functions (enable/disable certain features).
+
+    /// Configures whether or not our tauri app should show system
+    /// notifications. This excludes connection prompts. Use
+    /// with_connection_prompts to enable handling of connection prompts.
+    pub fn with_notification_support(&self, enable: bool) {
+        self.handle_notifications.store(enable, Ordering::Relaxed);
+
+        // kick of the notification handler if we are connected.
+        if enable {
+            self.start_notification_handler();
+        }
+    }
+
+    /// Configures whether or not our angular application should show connection
+    /// prompts via tauri.
+    pub fn with_connection_prompts(&self, enable: bool) {
+        self.handle_prompts.store(enable, Ordering::Relaxed);
+    }
+
+    /// Whether or not the angular application should call window.show after it
+    /// finished bootstrapping.
+    pub fn set_show_after_bootstrap(&self, show: bool) {
+        self.should_show_after_bootstrap
+            .store(show, Ordering::Relaxed);
+    }
+
+    /// Returns whether or not the angular application should call window.show
+    /// after it finished bootstrapping.
+    pub fn get_show_after_bootstrap(&self) -> bool {
+        self.should_show_after_bootstrap.load(Ordering::Relaxed)
+    }
+
+    /// Tells the angular applicatoin to show the window by emitting an event.
+    /// It calls set_show_after_bootstrap(true) automatically so the application
+    /// also shows after bootstrapping.
+    pub fn show_window(&self) {
+        debug!("[tauri] showing main window");
+
+        // set show_after_bootstrap to true so the app will even show if it
+        // misses the event below because it's still bootstrapping.
+        self.set_show_after_bootstrap(true);
+
+        // ignore the error here, there's nothing we could do about it anyways.
+        let _ = self.app.emit("portmaster:show", "");
+    }
+
+    /// Enables or disables the SPN.
+    pub fn set_spn_enabled(&self, enabled: bool) {
+        if let Some(api) = self.get_api() {
+            let body: Result<Payload, serde_json::Error> = BooleanValue {
+                value: Some(enabled),
+            }
+            .try_into();
+
+            if let Ok(payload) = body {
+                tauri::async_runtime::spawn(async move {
+                    _ = api
+                        .request(Request::Update("config:spn/enable".to_string(), payload))
+                        .await;
+                });
+            }
+        }
+    }
+
+    //// Internal functions
+    fn start_notification_handler(&self) {
+        if let Some(api) = self.get_api() {
+            let cli = api.clone();
+            tauri::async_runtime::spawn(async move {
+                notifications::notification_handler(cli).await;
+            });
+        }
+    }
+
+    /// Internal method to call all on_connect handlers
+    fn on_connect(&self, api: PortAPI) {
+        self.is_reachable.store(true, Ordering::Relaxed);
+
+        // store the new api client.
+        let mut guard = self.api.lock().unwrap();
+        *guard = Some(api.clone());
+        drop(guard);
+
+        // fire-off the notification handler.
+        if self.handle_notifications.load(Ordering::Relaxed) {
+            self.start_notification_handler();
+        }
+
+        if let Ok(mut handlers) = self.handlers.lock() {
+            for handler in handlers.iter_mut() {
+                handler.on_connect(api.clone());
+            }
+        }
+    }
+
+    /// Internal method to call all on_disconnect handlers
+    fn on_disconnect(&self) {
+        self.is_reachable.store(false, Ordering::Relaxed);
+
+        // clear the current api client reference.
+        let mut guard = self.api.lock().unwrap();
+        *guard = None;
+        drop(guard);
+
+        if let Ok(mut handlers) = self.handlers.lock() {
+            for handler in handlers.iter_mut() {
+                handler.on_disconnect();
+            }
+        }
+    }
+}
+
+pub trait PortmasterExt<R: Runtime> {
+    fn portmaster(&self) -> &PortmasterPlugin<R>;
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct Config {}
+
+impl<R: Runtime, T: Manager<R>> PortmasterExt<R> for T {
+    fn portmaster(&self) -> &PortmasterPlugin<R> {
+        self.state::<PortmasterPlugin<R>>().inner()
+    }
+}
+
+pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
+    Builder::<R, Option<Config>>::new("portmaster")
+        .invoke_handler(tauri::generate_handler![
+            commands::get_app_info,
+            commands::get_service_manager_status,
+            commands::start_service,
+            commands::get_state,
+            commands::set_state,
+            commands::should_show,
+            commands::should_handle_prompts
+        ])
+        .setup(|app, _api| {
+            let plugin = PortmasterPlugin {
+                app: app.clone(),
+                state: Mutex::new(HashMap::new()),
+                is_reachable: AtomicBool::new(false),
+                handlers: Mutex::new(Vec::new()),
+                api: Mutex::new(None),
+                handle_notifications: AtomicBool::new(false),
+                handle_prompts: AtomicBool::new(false),
+                should_show_after_bootstrap: AtomicBool::new(true),
+            };
+
+            app.manage(plugin);
+
+            // fire of the websocket handler
+            websocket::start_websocket_thread(app.clone());
+
+            Ok(())
+        })
+        .build()
+}
diff --git a/desktop/tauri/src-tauri/src/portmaster/notifications.rs b/desktop/tauri/src-tauri/src/portmaster/notifications.rs
new file mode 100644
index 00000000..88472639
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portmaster/notifications.rs
@@ -0,0 +1,103 @@
+use crate::portapi::client::*;
+use crate::portapi::message::*;
+use crate::portapi::models::notification::*;
+use crate::portapi::types::*;
+use log::error;
+use notify_rust;
+use serde_json::json;
+#[allow(unused_imports)]
+use tauri::async_runtime;
+
+pub async fn notification_handler(cli: PortAPI) {
+    let res = cli
+        .request(Request::QuerySubscribe("query notifications:".to_string()))
+        .await;
+
+    if let Ok(mut rx) = res {
+        while let Some(msg) = rx.recv().await {
+            let res = match msg {
+                Response::Ok(key, payload) => Some((key, payload)),
+                Response::New(key, payload) => Some((key, payload)),
+                Response::Update(key, payload) => Some((key, payload)),
+                _ => None,
+            };
+
+            if let Some((key, payload)) = res {
+                match payload.parse::<Notification>() {
+                    Ok(n) => {
+                        // Skip if this one should not be shown using the system notifications
+                        if !n.show_on_system {
+                            return;
+                        }
+
+                        // Skip if this action has already been acted on
+                        if n.selected_action_id != "" {
+                            return;
+                        }
+
+                        // TODO(ppacher): keep a reference of open notifications and close them
+                        // if the user reacted inside the UI:
+
+                        let mut notif = notify_rust::Notification::new();
+                        notif.body(&n.message);
+                        notif.timeout(notify_rust::Timeout::Never); // TODO(ppacher): use n.expires to calculate the timeout.
+                        notif.summary(&n.title);
+                        notif.icon("portmaster");
+
+                        for action in n.actions {
+                            notif.action(&action.id, &action.text);
+                        }
+
+                        #[cfg(target_os = "linux")]
+                        {
+                            let cli_clone = cli.clone();
+                            async_runtime::spawn(async move {
+                                let res = notif.show();
+                                match res {
+                                    Ok(handle) => {
+                                        handle.wait_for_action(|action| {
+                                            match action {
+                                                "__closed" => {
+                                                    // timeout
+                                                }
+
+                                                value => {
+                                                    let value = value.to_string().clone();
+
+                                                    async_runtime::spawn(async move {
+                                                        let _ = cli_clone
+                                                            .request(Request::Update(
+                                                                key,
+                                                                Payload::JSON(
+                                                                    json!({
+                                                                        "SelectedActionID": value
+                                                                    })
+                                                                    .to_string(),
+                                                                ),
+                                                            ))
+                                                            .await;
+                                                    });
+                                                }
+                                            }
+                                        })
+                                    }
+                                    Err(err) => {
+                                        error!("failed to display notification: {}", err);
+                                    }
+                                }
+                            });
+                        }
+                    }
+                    Err(err) => match err {
+                        ParseError::JSON(err) => {
+                            error!("failed to parse notification: {}", err);
+                        }
+                        _ => {
+                            error!("unknown error when parsing notifications payload");
+                        }
+                    },
+                }
+            }
+        }
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/portmaster/websocket.rs b/desktop/tauri/src-tauri/src/portmaster/websocket.rs
new file mode 100644
index 00000000..6dca8519
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/portmaster/websocket.rs
@@ -0,0 +1,45 @@
+use super::PortmasterExt;
+use crate::portapi::client::connect;
+use log::{debug, error, info, warn};
+use tauri::{AppHandle, Runtime};
+use tokio::time::{sleep, Duration};
+
+/// Starts a backround thread (via tauri::async_runtime) that connects to the Portmaster
+/// Websocket database API.
+pub fn start_websocket_thread<R: Runtime>(app: AppHandle<R>) {
+    let app = app.clone();
+
+    tauri::async_runtime::spawn(async move {
+        loop {
+            debug!("Trying to connect to websocket endpoint");
+
+            let api = connect("ws://127.0.0.1:817/api/database/v1").await;
+
+            match api {
+                Ok(cli) => {
+                    let portmaster = app.portmaster();
+
+                    info!("Successfully connected to portmaster");
+
+                    portmaster.on_connect(cli.clone());
+
+                    while !cli.is_closed() {
+                        let _ = sleep(Duration::from_secs(1)).await;
+                    }
+
+                    portmaster.on_disconnect();
+
+                    warn!("lost connection to portmaster, retrying ....")
+                }
+                Err(err) => {
+                    error!("failed to create portapi client: {}", err);
+
+                    app.portmaster().on_disconnect();
+
+                    // sleep and retry
+                    sleep(Duration::from_secs(2)).await;
+                }
+            }
+        }
+    });
+}
diff --git a/desktop/tauri/src-tauri/src/service/manager.rs b/desktop/tauri/src-tauri/src/service/manager.rs
new file mode 100644
index 00000000..7a79e061
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/service/manager.rs
@@ -0,0 +1,17 @@
+use std::process::{Command, ExitStatus, Stdio};
+use std::{fs, io};
+
+use thiserror::Error;
+
+#[cfg(target_os = "linux")]
+use std::os::unix::fs::PermissionsExt;
+
+use super::status::StatusResult;
+
+static SYSTEMCTL: &str = "systemctl";
+// TODO(ppacher): add support for kdesudo and gksudo
+
+enum SudoCommand {
+    Pkexec,
+    Gksu,
+}
diff --git a/desktop/tauri/src-tauri/src/service/mod.rs b/desktop/tauri/src-tauri/src/service/mod.rs
new file mode 100644
index 00000000..ac55a39f
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/service/mod.rs
@@ -0,0 +1,76 @@
+// pub mod manager;
+pub mod status;
+
+#[cfg(target_os = "linux")]
+mod systemd;
+
+#[cfg(target_os = "windows")]
+mod windows_service;
+
+use std::process::ExitStatus;
+
+#[cfg(target_os = "linux")]
+use crate::service::systemd::SystemdServiceManager;
+
+use log::info;
+use thiserror::Error;
+
+use self::status::StatusResult;
+
+#[allow(dead_code)]
+#[derive(Error, Debug)]
+pub enum ServiceManagerError {
+    #[error("unsupported service manager")]
+    UnsupportedServiceManager,
+
+    #[error("unsupported operating system")]
+    UnsupportedOperatingSystem,
+
+    #[error(transparent)]
+    FromUtf8Error(#[from] std::string::FromUtf8Error),
+
+    #[error(transparent)]
+    IoError(#[from] std::io::Error),
+
+    #[error("{0} output={1}")]
+    Other(ExitStatus, String),
+
+    #[error("{0}")]
+    WindowsError(String),
+}
+
+pub type Result<T> = std::result::Result<T, ServiceManagerError>;
+
+/// A common interface to the system manager service (might be systemd, openrc, sc.exe, ...)
+pub trait ServiceManager {
+    fn status(&self) -> Result<StatusResult>;
+    fn start(&self) -> Result<StatusResult>;
+}
+
+struct EmptyServiceManager();
+
+impl ServiceManager for EmptyServiceManager {
+    fn status(&self) -> Result<StatusResult> {
+        Err(ServiceManagerError::UnsupportedServiceManager)
+    }
+
+    fn start(&self) -> Result<StatusResult> {
+        Err(ServiceManagerError::UnsupportedServiceManager)
+    }
+}
+
+pub fn get_service_manager() -> Result<impl ServiceManager> {
+    #[cfg(target_os = "linux")]
+    {
+        if SystemdServiceManager::is_installed() {
+            info!("system service manager: systemd");
+
+            Ok(SystemdServiceManager {})
+        } else {
+            Err(ServiceManagerError::UnsupportedServiceManager)
+        }
+    }
+
+    #[cfg(target_os = "windows")]
+    return Ok(windows_service::SERVICE_MANGER.clone());
+}
diff --git a/desktop/tauri/src-tauri/src/service/status.rs b/desktop/tauri/src-tauri/src/service/status.rs
new file mode 100644
index 00000000..30f4841d
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/service/status.rs
@@ -0,0 +1,27 @@
+use serde::{Serialize, Deserialize};
+
+/// SystemResult defines the "success" codes when querying or starting
+/// a system service. 
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+pub enum StatusResult {
+    // The requested system service is installed and currently running.
+    Running,
+
+    // The requested system service is installed but currently stopped.
+    Stopped,
+
+    // NotFound is returned when the system service (systemd unit for linux)
+    // has not been found and the system and likely means the Portmaster installtion
+    // is broken all together. 
+    NotFound,
+}
+
+impl std::fmt::Display for StatusResult {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            StatusResult::Running => write!(f, "running"),
+            StatusResult::Stopped => write!(f, "stopped"),
+            StatusResult::NotFound => write!(f, "not installed")
+        }
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/service/systemd.rs b/desktop/tauri/src-tauri/src/service/systemd.rs
new file mode 100644
index 00000000..770a9139
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/service/systemd.rs
@@ -0,0 +1,246 @@
+use log::{debug, error};
+
+use super::status::StatusResult;
+use super::{Result, ServiceManager, ServiceManagerError};
+use std::os::unix::fs::PermissionsExt;
+use std::{
+    fs, io,
+    process::{Command, ExitStatus, Stdio},
+};
+
+static SYSTEMCTL: &str = "systemctl";
+// TODO(ppacher): add support for kdesudo and gksudo
+
+enum SudoCommand {
+    Pkexec,
+    Gksu,
+}
+
+impl From<std::process::Output> for ServiceManagerError {
+    fn from(output: std::process::Output) -> Self {
+        let msg = String::from_utf8(output.stderr)
+            .ok()
+            .filter(|s| !s.trim().is_empty())
+            .or_else(|| {
+                String::from_utf8(output.stdout)
+                    .ok()
+                    .filter(|s| !s.trim().is_empty())
+            })
+            .unwrap_or_else(|| format!("Failed to run `systemctl`"));
+
+        ServiceManagerError::Other(output.status, msg)
+    }
+}
+
+/// System Service manager implementation for Linux based distros.
+pub struct SystemdServiceManager {}
+
+impl SystemdServiceManager {
+    /// Checks if systemctl is available in /sbin/ /bin, /usr/bin or /usr/sbin.
+    ///
+    /// Note that we explicitly check those paths to avoid returning true in case
+    /// there's a systemctl binary in the cwd and PATH includes . since this may
+    /// pose a security risk of running an untrusted binary with root privileges.
+    pub fn is_installed() -> bool {
+        let paths = vec![
+            "/sbin/systemctl",
+            "/bin/systemctl",
+            "/usr/sbin/systemctl",
+            "/usr/bin/systemctl",
+        ];
+
+        for path in paths {
+            debug!("checking for systemctl at path {}", path);
+
+            match fs::metadata(path) {
+                Ok(md) => {
+                    debug!("found systemctl at path {} ", path);
+
+                    if md.is_file() && md.permissions().mode() & 0o111 != 0 {
+                        return true;
+                    }
+
+                    error!(
+                        "systemctl binary found but invalid permissions: {}",
+                        md.permissions().mode().to_string()
+                    );
+                }
+                Err(err) => {
+                    error!(
+                        "failed to check systemctl binary at {}: {}",
+                        path,
+                        err.to_string()
+                    );
+
+                    continue;
+                }
+            };
+        }
+
+        error!("failed to find systemctl binary");
+
+        false
+    }
+}
+
+impl ServiceManager for SystemdServiceManager {
+    fn status(&self) -> super::Result<StatusResult> {
+        let name = "portmaster.service";
+        let result = systemctl("is-active", name, false);
+
+        match result {
+            // If `systemctl is-active` returns without an error code and stdout matches "active" (just to guard againt
+            // unhandled cases), the service can be considered running.
+            Ok(stdout) => {
+                let mut copy = stdout.to_owned();
+                trim_newline(&mut copy);
+
+                if copy != "active" {
+                    // make sure the output is as we expected
+                    Err(ServiceManagerError::Other(ExitStatus::default(), stdout))
+                } else {
+                    Ok(StatusResult::Running)
+                }
+            }
+
+            Err(e) => {
+                if let ServiceManagerError::Other(_err, ref output) = e {
+                    let mut copy = output.to_owned();
+                    trim_newline(&mut copy);
+
+                    if copy == "inactive" {
+                        return Ok(StatusResult::Stopped);
+                    }
+                } else {
+                    error!("failed to run 'systemctl is-active': {}", e.to_string());
+                }
+
+                // Failed to check if the unit is running
+                match systemctl("cat", name, false) {
+                    // "systemctl cat" seems to no have stable exit codes so we need
+                    // to check the output if it looks like "No files found for yyyy.service"
+                    // At least, the exit code are not documented for systemd v255 (newest at the time of writing)
+                    Err(ServiceManagerError::Other(status, msg)) => {
+                        if msg.contains("No files found for") {
+                            Ok(StatusResult::NotFound)
+                        } else {
+                            Err(ServiceManagerError::Other(status, msg))
+                        }
+                    }
+
+                    // Any other error type means something went completely wrong while running systemctl altogether.
+                    Err(e) => Err(e),
+
+                    // Fine, systemctl cat worked so if the output is "inactive" we know the service is installed
+                    // but stopped.
+                    Ok(_) => {
+                        // Unit seems to be installed so check the output of result
+                        let mut stderr = e.to_string();
+                        trim_newline(&mut stderr);
+
+                        if stderr == "inactive" {
+                            Ok(StatusResult::Stopped)
+                        } else {
+                            Err(e)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn start(&self) -> Result<StatusResult> {
+        let name = "portmaster.service";
+
+        // This time we need to run as root through pkexec or similar binaries like kdesudo/gksudo.
+        systemctl("start", name, true)?;
+
+        // Check the status again to be sure it's started now
+        self.status()
+    }
+}
+
+fn systemctl(
+    cmd: &str,
+    unit: &str,
+    run_as_root: bool,
+) -> std::result::Result<String, ServiceManagerError> {
+    let output = run(run_as_root, SYSTEMCTL, vec![cmd, unit])?;
+
+    // The command have been able to run (i.e. has been spawned and executed by the kernel).
+    // We now need to check the exit code and "stdout/stderr" output in case of an error.
+    if output.status.success() {
+        Ok(String::from_utf8(output.stdout)?)
+    } else {
+        Err(output.into())
+    }
+}
+
+fn run<'a>(root: bool, cmd: &'a str, args: Vec<&'a str>) -> std::io::Result<std::process::Output> {
+    // clone the args vector so we can insert the actual command in case we're running
+    // through pkexec or friends. This is just callled a couple of times on start-up
+    // so cloning the vector does not add any mentionable performance impact here and it's better
+    // than expecting a mutalble vector in the first place.
+
+    let mut args = args.to_vec();
+
+    let mut command = match root {
+        true => {
+            // if we run through pkexec and friends we need to append cmd as the second argument.
+
+            args.insert(0, cmd);
+            match get_sudo_cmd() {
+                Ok(cmd) => {
+                    match cmd {
+                        SudoCommand::Pkexec => {
+                            // disable the internal text-based prompt agent from pkexec because it won't work anyway.
+                            args.insert(0, "--disable-internal-agent");
+                            Command::new("/usr/bin/pkexec")
+                        }
+                        SudoCommand::Gksu => {
+                            args.insert(0, "--message=Please enter your password:");
+                            args.insert(1, "--sudo-mode");
+
+                            Command::new("/usr/bin/gksudo")
+                        }
+                    }
+                }
+                Err(err) => return Err(err),
+            }
+        }
+        false => Command::new(cmd),
+    };
+
+    command.env("LC_ALL", "C");
+
+    command
+        .stdin(Stdio::null())
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped());
+
+    command.args(args).output()
+}
+
+fn trim_newline(s: &mut String) {
+    if s.ends_with('\n') {
+        s.pop();
+        if s.ends_with('\r') {
+            s.pop();
+        }
+    }
+}
+
+fn get_sudo_cmd() -> std::result::Result<SudoCommand, std::io::Error> {
+    if let Ok(_) = fs::metadata("/usr/bin/pkexec") {
+        return Ok(SudoCommand::Pkexec);
+    }
+
+    if let Ok(_) = fs::metadata("/usr/bin/gksudo") {
+        return Ok(SudoCommand::Gksu);
+    }
+
+    Err(std::io::Error::new(
+        io::ErrorKind::NotFound,
+        "failed to detect sudo command",
+    ))
+}
diff --git a/desktop/tauri/src-tauri/src/service/windows_service.rs b/desktop/tauri/src-tauri/src/service/windows_service.rs
new file mode 100644
index 00000000..2146cc41
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/service/windows_service.rs
@@ -0,0 +1,167 @@
+use std::{
+    sync::{Arc, Mutex},
+    time::Duration,
+};
+
+use windows::{
+    core::{HSTRING, PCWSTR},
+    Win32::{Foundation::HWND, UI::WindowsAndMessaging::SHOW_WINDOW_CMD},
+};
+use windows_service::{
+    service::{Service, ServiceAccess},
+    service_manager::{ServiceManager, ServiceManagerAccess},
+};
+
+const SERVICE_NAME: &str = "PortmasterCore";
+
+pub struct WindowsServiceManager {
+    manager: Option<ServiceManager>,
+    service: Option<Service>,
+}
+
+lazy_static! {
+    pub static ref SERVICE_MANGER: Arc<Mutex<WindowsServiceManager>> =
+        Arc::new(Mutex::new(WindowsServiceManager::new()));
+}
+
+impl WindowsServiceManager {
+    pub fn new() -> Self {
+        Self {
+            manager: None,
+            service: None,
+        }
+    }
+
+    fn init_manager(&mut self) -> super::Result<()> {
+        // Initialize service manager. This connects to the active service database and can query status.
+        let manager = match ServiceManager::local_computer(
+            None::<&str>,
+            ServiceManagerAccess::ENUMERATE_SERVICE, // Only query status is allowed form non privileged application.
+        ) {
+            Ok(manager) => manager,
+            Err(err) => {
+                return Err(windows_to_manager_err(err));
+            }
+        };
+        self.manager = Some(manager);
+        Ok(())
+    }
+
+    fn open_service(&mut self) -> super::Result<bool> {
+        if let None = self.manager {
+            self.init_manager()?;
+        }
+
+        if let Some(manager) = &self.manager {
+            let service = match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
+                Ok(service) => service,
+                Err(_) => {
+                    return Ok(false); // Service is not installed.
+                }
+            };
+            // Service is installed and the state can be queried.
+            self.service = Some(service);
+            return Ok(true);
+        }
+
+        return Err(super::ServiceManagerError::WindowsError(
+            "failed to initialize manager".to_string(),
+        ));
+    }
+}
+
+impl super::ServiceManager for Arc<Mutex<WindowsServiceManager>> {
+    fn status(&self) -> super::Result<super::status::StatusResult> {
+        if let Ok(mut manager) = self.lock() {
+            if let None = manager.service {
+                // Try to open service
+                if !manager.open_service()? {
+                    // Service is not installed.
+                    return Ok(super::status::StatusResult::NotFound);
+                }
+            }
+
+            if let Some(service) = &manager.service {
+                match service.query_status() {
+                    Ok(status) => match status.current_state {
+                        windows_service::service::ServiceState::Stopped
+                        | windows_service::service::ServiceState::StopPending
+                        | windows_service::service::ServiceState::PausePending
+                        | windows_service::service::ServiceState::StartPending
+                        | windows_service::service::ServiceState::ContinuePending
+                        | windows_service::service::ServiceState::Paused => {
+                            // Stopped or in a transition state.
+                            return Ok(super::status::StatusResult::Stopped);
+                        }
+                        windows_service::service::ServiceState::Running => {
+                            // Everything expect Running state is considered stopped.
+                            return Ok(super::status::StatusResult::Running);
+                        }
+                    },
+                    Err(err) => {
+                        return Err(super::ServiceManagerError::WindowsError(err.to_string()));
+                    }
+                }
+            }
+        }
+        // This should be unreachable.
+        Ok(super::status::StatusResult::NotFound)
+    }
+
+    fn start(&self) -> super::Result<super::status::StatusResult> {
+        if let Ok(mut service_manager) = self.lock() {
+            // Check if service is installed.
+            if let None = &service_manager.service {
+                if let Err(_) = service_manager.open_service() {
+                    return Ok(super::status::StatusResult::NotFound);
+                }
+            }
+
+            // Run service manager with elevated privileges. This will show access popup.
+            unsafe {
+                windows::Win32::UI::Shell::ShellExecuteW(
+                    HWND::default(),
+                    &HSTRING::from("runas"),
+                    &HSTRING::from("C:\\Windows\\System32\\sc.exe"),
+                    &HSTRING::from(format!("start {}", SERVICE_NAME)),
+                    PCWSTR::null(),
+                    SHOW_WINDOW_CMD(0),
+                );
+            }
+
+            // Wait for service to start. Timeout 10s (100 * 100ms).
+            if let Some(service) = &service_manager.service {
+                for _ in 0..100 {
+                    match service.query_status() {
+                        Ok(status) => {
+                            if let windows_service::service::ServiceState::Running =
+                                status.current_state
+                            {
+                                return Ok(super::status::StatusResult::Running);
+                            } else {
+                                std::thread::sleep(Duration::from_millis(100));
+                            }
+                        }
+                        Err(err) => return Err(windows_to_manager_err(err)),
+                    }
+                }
+            }
+            // Timeout starting the service.
+            return Ok(super::status::StatusResult::Stopped);
+        }
+        return Err(super::ServiceManagerError::WindowsError(
+            "failed to start service".to_string(),
+        ));
+    }
+}
+
+fn windows_to_manager_err(err: windows_service::Error) -> super::ServiceManagerError {
+    if let windows_service::Error::Winapi(_) = err {
+        // Winapi does not contain the full error. Get the actual error from windows.
+        return super::ServiceManagerError::WindowsError(
+            windows::core::Error::from_win32().to_string(), // Internally will call `GetLastError()` and parse the result.
+        );
+    } else {
+        return super::ServiceManagerError::WindowsError(err.to_string());
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/traymenu.rs b/desktop/tauri/src-tauri/src/traymenu.rs
new file mode 100644
index 00000000..6456797f
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/traymenu.rs
@@ -0,0 +1,344 @@
+use std::collections::HashMap;
+use std::sync::Mutex;
+
+use log::{debug, error};
+use tauri::{
+    menu::{
+        CheckMenuItem, CheckMenuItemBuilder, MenuBuilder, MenuItemBuilder, PredefinedMenuItem,
+        SubmenuBuilder,
+    },
+    tray::{ClickType, TrayIcon, TrayIconBuilder},
+    Icon, Manager, Wry,
+};
+use tauri_plugin_dialog::DialogExt;
+
+use crate::{
+    portapi::{
+        client::PortAPI,
+        message::ParseError,
+        models::{
+            config::BooleanValue,
+            spn::SPNStatus,
+            subsystem::{self, Subsystem},
+        },
+        types::{Request, Response},
+    },
+    portmaster::PortmasterExt,
+    window::{create_main_window, may_navigate_to_ui, open_window},
+};
+
+pub type AppIcon = TrayIcon<Wry>;
+
+lazy_static! {
+    // Set once setup_tray_menu executed.
+    static ref SPN_BUTTON: Mutex<Option<CheckMenuItem<Wry>>> = Mutex::new(None);
+}
+
+// Icons
+//
+const BLUE_ICON: &'static [u8] =
+    include_bytes!("../../assets/icons/pm_light_blue_512.ico");
+const RED_ICON: &'static [u8] =
+    include_bytes!("../../assets/icons/pm_light_red_512.ico");
+const YELLOW_ICON: &'static [u8] =
+    include_bytes!("../../assets/icons/pm_light_yellow_512.ico");
+const GREEN_ICON: &'static [u8] =
+    include_bytes!("../../assets/icons/pm_light_green_512.ico");
+
+pub fn setup_tray_menu(
+    app: &mut tauri::App,
+) -> core::result::Result<AppIcon, Box<dyn std::error::Error>> {
+    // Tray menu
+    let close_btn = MenuItemBuilder::with_id("close", "Exit").build(app);
+    let open_btn = MenuItemBuilder::with_id("open", "Open").build(app);
+
+    let spn = CheckMenuItemBuilder::with_id("spn", "Use SPN").build(app);
+
+    // Store the SPN button reference
+    let mut button_ref = SPN_BUTTON.lock().unwrap();
+    *button_ref = Some(spn.clone());
+
+    let force_show_window = MenuItemBuilder::with_id("force-show", "Force Show UI").build(app);
+    let reload_btn = MenuItemBuilder::with_id("reload", "Reload User Interface").build(app);
+    let developer_menu = SubmenuBuilder::new(app, "Developer")
+        .items(&[&reload_btn, &force_show_window])
+        .build()?;
+
+    // Drop the reference now so we unlock immediately.
+    drop(button_ref);
+
+    let menu = MenuBuilder::new(app)
+        .items(&[
+            &spn,
+            &PredefinedMenuItem::separator(app),
+            &open_btn,
+            &close_btn,
+            &developer_menu,
+        ])
+        .build()?;
+
+    let icon = TrayIconBuilder::new()
+        .icon(Icon::Raw(RED_ICON.to_vec()))
+        .menu(&menu)
+        .on_menu_event(move |app, event| match event.id().as_ref() {
+            "close" => {
+                let handle = app.clone();
+                app.dialog()
+                    .message("This does not stop the Portmaster system service")
+                    .title("Do you really want to quit the user interface?")
+                    .ok_button_label("Yes, exit")
+                    .cancel_button_label("No")
+                    .show(move |answer| {
+                        if answer {
+                            let _ = handle.emit("exit-requested", "");
+                            handle.exit(0);
+                        }
+                    });
+            }
+            "open" => {
+                let _ = open_window(app);
+            }
+            "reload" => {
+                if let Ok(mut win) = open_window(app) {
+                    may_navigate_to_ui(&mut win, true);
+                }
+            }
+            "force-show" => {
+                match create_main_window(app) {
+                    Ok(mut win) => {
+                        may_navigate_to_ui(&mut win, true);
+                        if let Err(err) = win.show() {
+                            error!("[tauri] failed to show window: {}", err.to_string());
+                        };
+                    }
+                    Err(err) => {
+                        error!("[tauri] failed to create main window: {}", err.to_string());
+                    }
+                };
+            }
+            "spn" => {
+                let btn = SPN_BUTTON.lock().unwrap();
+
+                if let Some(bt) = &*btn {
+                    if let Ok(is_checked) = bt.is_checked() {
+                        app.portmaster().set_spn_enabled(is_checked);
+                    }
+                }
+            }
+            other => {
+                error!("unknown menu event id: {}", other);
+            }
+        })
+        .on_tray_icon_event(|tray, event| {
+            // not supported on linux
+            if event.click_type == ClickType::Left {
+                let _ = open_window(tray.app_handle());
+            }
+        })
+        .build(app)?;
+
+    Ok(icon)
+}
+
+pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_status: String) {
+    // iterate over the subsytems and check if there's a module failure
+    let failure = subsystems
+        .values()
+        .into_iter()
+        .map(|s| s.failure_status)
+        .fold(
+            subsystem::FAILURE_NONE,
+            |acc, s| {
+                if s > acc {
+                    s
+                } else {
+                    acc
+                }
+            },
+        );
+
+    let next_icon = match failure {
+        subsystem::FAILURE_WARNING => YELLOW_ICON,
+        subsystem::FAILURE_ERROR => RED_ICON,
+        _ => match spn_status.as_str() {
+            "connected" | "connecting" => BLUE_ICON,
+            _ => GREEN_ICON,
+        },
+    };
+
+    _ = icon.set_icon(Some(Icon::Raw(next_icon.to_vec())));
+}
+
+pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
+    let icon = match app.tray() {
+        Some(icon) => icon,
+        None => {
+            error!("cancel try_handler: missing try icon");
+            return;
+        }
+    };
+
+    let mut subsystem_subscription = match cli
+        .request(Request::QuerySubscribe(
+            "query runtime:subsystems/".to_string(),
+        ))
+        .await
+    {
+        Ok(rx) => rx,
+        Err(err) => {
+            error!(
+                "cancel try_handler: failed to subscribe to 'runtime:subsystems': {}",
+                err
+            );
+            return;
+        }
+    };
+
+    let mut spn_status_subscription = match cli
+        .request(Request::QuerySubscribe(
+            "query runtime:spn/status".to_string(),
+        ))
+        .await
+    {
+        Ok(rx) => rx,
+        Err(err) => {
+            error!(
+                "cancel try_handler: failed to subscribe to 'runtime:spn/status': {}",
+                err
+            );
+            return;
+        }
+    };
+
+    let mut spn_config_subscription = match cli
+        .request(Request::QuerySubscribe(
+            "query config:spn/enable".to_string(),
+        ))
+        .await
+    {
+        Ok(rx) => rx,
+        Err(err) => {
+            error!(
+                "cancel try_handler: failed to subscribe to 'runtime:spn/enable': {}",
+                err
+            );
+            return;
+        }
+    };
+
+    _ = icon.set_icon(Some(Icon::Raw(BLUE_ICON.to_vec())));
+
+    let mut subsystems: HashMap<String, Subsystem> = HashMap::new();
+    let mut spn_status: String = "".to_string();
+
+    loop {
+        tokio::select! {
+            msg = subsystem_subscription.recv() => {
+                let msg = match msg {
+                    Some(m) => m,
+                    None => { break }
+                };
+
+                let res = match msg {
+                    Response::Ok(key, payload) => Some((key, payload)),
+                    Response::New(key, payload) => Some((key, payload)),
+                    Response::Update(key, payload) => Some((key, payload)),
+                    _ => None,
+                };
+
+                if let Some((_, payload)) = res {
+                    match payload.parse::<Subsystem>() {
+                        Ok(n) => {
+                            subsystems.insert(n.id.clone(), n);
+
+                            update_icon(icon.clone(), subsystems.clone(), spn_status.clone());
+                        },
+                        Err(err) => match err {
+                            ParseError::JSON(err) => {
+                                error!("failed to parse subsystem: {}", err);
+                            }
+                            _ => {
+                                error!("unknown error when parsing notifications payload");
+                            }
+                        },
+                    }
+                }
+            },
+            msg = spn_status_subscription.recv() => {
+                let msg = match msg {
+                    Some(m) => m,
+                    None => { break }
+                };
+
+                let res = match msg {
+                    Response::Ok(key, payload) => Some((key, payload)),
+                    Response::New(key, payload) => Some((key, payload)),
+                    Response::Update(key, payload) => Some((key, payload)),
+                    _ => None,
+                };
+
+                if let Some((_, payload)) = res {
+                    match payload.parse::<SPNStatus>() {
+                        Ok(value) => {
+                            debug!("SPN status update: {}", value.status);
+                            spn_status = value.status.clone();
+
+                            update_icon(icon.clone(), subsystems.clone(), spn_status.clone());
+                        },
+                        Err(err) => match err {
+                            ParseError::JSON(err) => {
+                                error!("failed to parse spn status value: {}", err)
+                            },
+                            _ => {
+                                error!("unknown error when parsing spn status value")
+                            }
+                        }
+                    }
+                }
+            },
+            msg = spn_config_subscription.recv() => {
+                let msg = match msg {
+                    Some(m) => m,
+                    None => { break }
+                };
+
+                let res = match msg {
+                    Response::Ok(key, payload) => Some((key, payload)),
+                    Response::New(key, payload) => Some((key, payload)),
+                    Response::Update(key, payload) => Some((key, payload)),
+                    _ => None,
+                };
+
+                if let Some((_, payload)) = res {
+                    match payload.parse::<BooleanValue>() {
+                        Ok(value) => {
+                            let mut btn = SPN_BUTTON.lock().unwrap();
+
+                            if let Some(btn) = &mut *btn {
+                                if let Some(value) = value.value {
+                                    _ = btn.set_checked(value);
+                                } else {
+                                    _ = btn.set_checked(false);
+                                }
+                            }
+                        },
+                        Err(err) => match err {
+                            ParseError::JSON(err) => {
+                                error!("failed to parse config value: {}", err)
+                            },
+                            _ => {
+                                error!("unknown error when parsing config value")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if let Some(btn) = &mut *(SPN_BUTTON.lock().unwrap()) {
+        _ = btn.set_checked(false);
+    }
+
+    _ = icon.set_icon(Some(Icon::Raw(RED_ICON.to_vec())));
+}
diff --git a/desktop/tauri/src-tauri/src/window.rs b/desktop/tauri/src-tauri/src/window.rs
new file mode 100644
index 00000000..2059a134
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/window.rs
@@ -0,0 +1,151 @@
+use log::{debug, error};
+use tauri::{AppHandle, Manager, Result, UserAttentionType, Window, WindowBuilder, WindowUrl};
+
+use crate::portmaster::PortmasterExt;
+
+/// Either returns the existing "main" window or creates a new one.
+///
+/// The window is not automatically shown (i.e it starts hidden).
+/// If a new main window is created (i.e. the tauri app was minimized to system-tray)
+/// then the window will be automatically navigated to the Portmaster UI endpoint
+/// if ::websocket::is_portapi_reachable returns true.
+///
+/// Either the existing or the newly created window is returned.
+pub fn create_main_window(app: &AppHandle) -> Result<Window> {
+    let mut window = if let Some(window) = app.get_window("main") {
+        debug!("[tauri] main window already created");
+
+        window
+    } else {
+        debug!("[tauri] creating main window");
+
+        let res = WindowBuilder::new(app, "main", WindowUrl::App("index.html".into()))
+            .visible(false)
+            .build();
+
+        match res {
+            Ok(win) => {
+                win.once("tauri://error", |event| {
+                    error!("failed to open tauri window: {}", event.payload());
+                });
+
+                win
+            }
+            Err(err) => {
+                error!("[tauri] failed to create main window: {}", err.to_string());
+
+                return Err(err);
+            }
+        }
+    };
+
+    // If the window is not yet navigated to the Portmaster UI, do it now.
+    may_navigate_to_ui(&mut window, false);
+
+    #[cfg(debug_assertions)]
+    if let Ok(_) = std::env::var("TAURI_SHOW_IMMEDIATELY") {
+        debug!("[tauri] TAURI_SHOW_IMMEDIATELY is set, opening window");
+
+        if let Err(err) = window.show() {
+            error!("[tauri] failed to show window: {}", err.to_string());
+        }
+    }
+
+    Ok(window)
+}
+
+pub fn create_splash_window(app: &AppHandle) -> Result<Window> {
+    if let Some(window) = app.get_window("splash") {
+        let _ = window.show();
+        Ok(window)
+    } else {
+        let window = WindowBuilder::new(app, "splash", WindowUrl::App("index.html".into()))
+            .center()
+            .closable(false)
+            .focused(true)
+            .resizable(false)
+            .visible(true)
+            .title("Portmaster")
+            .inner_size(600.0, 250.0)
+            .build()?;
+
+        let _ = window.request_user_attention(Some(UserAttentionType::Informational));
+
+        Ok(window)
+    }
+}
+
+pub fn close_splash_window(app: &AppHandle) -> Result<()> {
+    if let Some(window) = app.get_window("splash") {
+        return window.close();
+    }
+    return Err(tauri::Error::WindowNotFound);
+}
+
+/// Opens a window for the tauri application.
+///
+/// If the main window has already been created, it is instructed to
+/// show even if we're currently not connected to Portmaster.
+/// This is safe since the main-window will only be created if Portmaster API
+/// was reachable so the angular application must have finished bootstrapping.
+///
+/// If there's not main window and the Portmaster API is reachable we create a new
+/// main window.
+///
+/// If the Portmaster API is unreachable and there's no main window yet, we show the
+/// splash-screen window.
+pub fn open_window(app: &AppHandle) -> Result<Window> {
+    if app.portmaster().is_reachable() {
+        match app.get_window("main") {
+            Some(win) => {
+                app.portmaster().show_window();
+
+                Ok(win)
+            }
+            None => {
+                app.portmaster().show_window();
+
+                create_main_window(app)
+            }
+        }
+    } else {
+        debug!("Show splash screen");
+        create_splash_window(app)
+    }
+}
+
+/// If the Portmaster Websocket database API is reachable the window will be navigated
+/// to the HTTP endpoint of Portmaster to load the UI from there.
+///
+/// Note that only happens if the window URL does not already point to the PM API.
+///
+/// In #[cfg(debug_assertions)] the TAURI_PM_URL environment variable will be used
+/// if set.
+/// Otherwise or in release builds, it will be navigated to http://127.0.0.1:817.
+pub fn may_navigate_to_ui(win: &mut Window, force: bool) {
+    if !win.app_handle().portmaster().is_reachable() && !force {
+        error!("[tauri] portmaster API is not reachable, not navigating");
+
+        return;
+    }
+
+    if force || cfg!(debug_assertions) || win.url().host_str() != Some("localhost") {
+        #[cfg(debug_assertions)]
+        if let Ok(target_url) = std::env::var("TAURI_PM_URL") {
+            debug!("[tauri] navigating to {}", target_url);
+
+            win.navigate(target_url.parse().unwrap());
+
+            return;
+        }
+
+        #[cfg(debug_assertions)]
+        {
+            debug!("[tauri] navigating to http://localhost:4200");
+            win.navigate("http://localhost:4200".parse().unwrap());
+        }
+
+        #[cfg(not(debug_assertions))]
+        win.navigate("http://localhost:817".parse().unwrap());
+    }
+}
diff --git a/desktop/tauri/src-tauri/src/xdg/mod.rs b/desktop/tauri/src-tauri/src/xdg/mod.rs
new file mode 100644
index 00000000..b1fa9089
--- /dev/null
+++ b/desktop/tauri/src-tauri/src/xdg/mod.rs
@@ -0,0 +1,585 @@
+use cached::proc_macro::once;
+use dataurl::DataUrl;
+use gdk_pixbuf::{Pixbuf, PixbufError};
+use gtk_sys::{
+    gtk_icon_info_free, gtk_icon_info_get_filename, gtk_icon_theme_get_default,
+    gtk_icon_theme_lookup_icon, GtkIconTheme,
+};
+use log::{debug, error};
+use std::collections::HashMap;
+use std::ffi::c_int;
+use std::ffi::{CStr, CString};
+use std::io;
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, RwLock};
+use std::{
+    env, fs,
+    io::{Error, ErrorKind},
+};
+use thiserror::Error;
+
+use dirs;
+use ini::{Ini, ParseOption};
+
+static mut GTK_DEFAULT_THEME: Option<*mut GtkIconTheme> = None;
+
+lazy_static! {
+    static ref APP_INFO_CACHE: Arc<RwLock<HashMap<String, Option<AppInfo>>>> =
+        Arc::new(RwLock::new(HashMap::new()));
+}
+
+#[derive(Debug, Error)]
+pub enum LookupError {
+    #[error(transparent)]
+    IoError(#[from] std::io::Error),
+}
+
+pub type Result<T> = std::result::Result<T, LookupError>;
+
+#[derive(Clone, serde::Serialize)]
+pub struct AppInfo {
+    pub icon_name: String,
+    pub app_name: String,
+    pub icon_dataurl: String,
+    pub comment: String,
+}
+
+impl Default for AppInfo {
+    fn default() -> Self {
+        AppInfo {
+            icon_dataurl: "".to_string(),
+            icon_name: "".to_string(),
+            app_name: "".to_string(),
+            comment: "".to_string(),
+        }
+    }
+}
+
+#[derive(Clone, serde::Serialize, Debug)]
+pub struct ProcessInfo {
+    pub exec_path: String,
+    pub cmdline: String,
+    pub pid: i64,
+    pub matching_path: String,
+}
+
+impl std::fmt::Display for ProcessInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{} (cmdline={}) (pid={}) (matching_path={})",
+            self.exec_path, self.cmdline, self.pid, self.matching_path
+        )
+    }
+}
+
+pub fn get_app_info(process_info: ProcessInfo) -> Result<AppInfo> {
+    {
+        let cache = APP_INFO_CACHE.read().unwrap();
+
+        if let Some(value) = cache.get(process_info.exec_path.as_str()) {
+            match value {
+                Some(app_info) => return Ok(app_info.clone()),
+                None => {
+                    return Err(LookupError::IoError(io::Error::new(
+                        io::ErrorKind::NotFound,
+                        "not found",
+                    )))
+                }
+            }
+        }
+    }
+
+    let mut needles = Vec::new();
+    if !process_info.exec_path.is_empty() {
+        needles.push(process_info.exec_path.as_str())
+    }
+    if !process_info.cmdline.is_empty() {
+        needles.push(process_info.cmdline.as_str())
+    }
+    if !process_info.matching_path.is_empty() {
+        needles.push(process_info.matching_path.as_str())
+    }
+
+    // sort and deduplicate
+    needles.sort();
+    needles.dedup();
+
+    debug!("Searching app info for {:?}", process_info);
+
+    let mut desktop_files = Vec::new();
+    for dir in get_application_directories()? {
+        let mut files = find_desktop_files(dir.as_path())?;
+        desktop_files.append(&mut files);
+    }
+
+    let mut matches = Vec::new();
+    for needle in needles.clone() {
+        debug!("Trying needle {} on exec path", needle);
+
+        match try_get_app_info(needle, CheckType::Exec, &desktop_files) {
+            Ok(mut result) => {
+                matches.append(&mut result);
+            }
+            Err(LookupError::IoError(ioerr)) => {
+                if ioerr.kind() != ErrorKind::NotFound {
+                    return Err(ioerr.into());
+                }
+            }
+        };
+
+        match try_get_app_info(needle, CheckType::Name, &desktop_files) {
+            Ok(mut result) => {
+                matches.append(&mut result);
+            }
+            Err(LookupError::IoError(ioerr)) => {
+                if ioerr.kind() != ErrorKind::NotFound {
+                    return Err(ioerr.into());
+                }
+            }
+        };
+    }
+
+    if matches.is_empty() {
+        APP_INFO_CACHE
+            .write()
+            .unwrap()
+            .insert(process_info.exec_path, None);
+
+        Err(Error::new(ErrorKind::NotFound, format!("failed to find app info")).into())
+    } else {
+        // sort matches by length
+        matches.sort_by(|a, b| a.1.cmp(&b.1));
+
+        for mut info in matches {
+            match get_icon_as_png_dataurl(&info.0.icon_name, 32) {
+                Ok(du) => {
+                    debug!(
+                        "[xdg] best match for {:?} is {:?} with len {}",
+                        process_info, info.0.icon_name, info.1
+                    );
+
+                    info.0.icon_dataurl = du.1;
+
+                    APP_INFO_CACHE
+                        .write()
+                        .unwrap()
+                        .insert(process_info.exec_path, Some(info.0.clone()));
+
+                    return Ok(info.0);
+                }
+                Err(err) => {
+                    error!(
+                        "{}: failed to get icon: {}",
+                        info.0.icon_name,
+                        err.to_string()
+                    );
+                }
+            };
+        }
+
+        Err(Error::new(ErrorKind::NotFound, format!("failed to find app info")).into())
+    }
+}
+
+/// Returns a vector of application directories that are expected
+/// to contain all .desktop files the current user has access to.
+/// The result of this function is cached for 5 minutes as it's not expected
+/// that application directories actually change.
+#[once(time = 300, sync_writes = true, result = true)]
+fn get_application_directories() -> Result<Vec<PathBuf>> {
+    let xdg_home = match env::var_os("XDG_DATA_HOME") {
+        Some(path) => PathBuf::from(path),
+        None => {
+            let home = dirs::home_dir()
+                .ok_or(Error::new(ErrorKind::Other, "Failed to get home directory"))?;
+
+            home.join(".local/share")
+        }
+    };
+
+    let extra_application_dirs = match env::var_os("XDG_DATA_DIRS") {
+        Some(paths) => env::split_paths(&paths).map(PathBuf::from).collect(),
+        None => {
+            // Fallback if XDG_DATA_DIRS is not set. If it's set, it normally already contains /usr/share and
+            // /usr/local/share
+            vec![
+                PathBuf::from("/usr/share"),
+                PathBuf::from("/usr/local/share"),
+            ]
+        }
+    };
+
+    let mut app_dirs = Vec::new();
+    for extra_dir in extra_application_dirs {
+        app_dirs.push(extra_dir.join("applications"));
+    }
+
+    app_dirs.push(xdg_home.join("applications"));
+
+    Ok(app_dirs)
+}
+
+// TODO(ppacher): cache the result of find_desktop_files as well.
+// Though, seems like we cannot use the #[cached::proc_macro::cached] or #[cached::proc_macro::once] macros here
+// because [`Result<Vec<fs::DirEntry>>>`] does not implement [`Clone`]
+fn find_desktop_files(path: &Path) -> Result<Vec<fs::DirEntry>> {
+    match path.read_dir() {
+        Ok(files) => {
+            let desktop_files = files
+                .filter_map(|entry| entry.ok())
+                .filter(|entry| match entry.file_type() {
+                    Ok(ft) => ft.is_file() || ft.is_symlink(),
+                    _ => false,
+                })
+                .filter(|entry| entry.file_name().to_string_lossy().ends_with(".desktop"))
+                .collect::<Vec<_>>();
+
+            Ok(desktop_files)
+        }
+        Err(err) => {
+            // We ignore NotFound errors here because not all application
+            // directories need to exist.
+            if err.kind() == ErrorKind::NotFound {
+                Ok(Vec::new())
+            } else {
+                Err(err.into())
+            }
+        }
+    }
+}
+
+enum CheckType {
+    Name,
+    Exec,
+}
+
+fn try_get_app_info(
+    needle: &str,
+    check: CheckType,
+    desktop_files: &Vec<fs::DirEntry>,
+) -> Result<Vec<(AppInfo, usize)>> {
+    let path = PathBuf::from(needle);
+
+    let file_name = path.as_path().file_name().unwrap_or_default().to_str();
+
+    let mut result = Vec::new();
+
+    for file in desktop_files {
+        let content = Ini::load_from_file_opt(
+            file.path(),
+            ParseOption {
+                enabled_escape: false,
+                enabled_quote: true,
+            },
+        )
+        .map_err(|err| Error::new(ErrorKind::Other, err.to_string()))?;
+
+        let desktop_section = match content.section(Some("Desktop Entry")) {
+            Some(section) => section,
+            None => {
+                continue;
+            }
+        };
+
+        let matches = match check {
+            CheckType::Name => {
+                let name = match desktop_section.get("Name") {
+                    Some(name) => name,
+                    None => {
+                        continue;
+                    }
+                };
+
+                if let Some(file_name) = file_name {
+                    if name.to_lowercase().contains(file_name) {
+                        file_name.len()
+                    } else {
+                        0
+                    }
+                } else {
+                    0
+                }
+            }
+            CheckType::Exec => {
+                let exec = match desktop_section.get("Exec") {
+                    Some(exec) => exec,
+                    None => {
+                        continue;
+                    }
+                };
+
+                if exec.to_lowercase().contains(needle) {
+                    needle.len()
+                } else if let Some(file_name) = file_name {
+                    if exec.to_lowercase().starts_with(file_name) {
+                        file_name.len()
+                    } else {
+                        0
+                    }
+                } else {
+                    0
+                }
+            }
+        };
+
+        if matches > 0 {
+            debug!(
+                "[xdg] found matching desktop for needle {} file at {}",
+                needle,
+                file.path().to_string_lossy()
+            );
+
+            let info = parse_app_info(desktop_section);
+
+            result.push((info, matches));
+        }
+    }
+
+    if result.len() > 0 {
+        Ok(result)
+    } else {
+        Err(Error::new(ErrorKind::NotFound, "no matching .desktop files found").into())
+    }
+}
+
+fn parse_app_info(props: &ini::Properties) -> AppInfo {
+    AppInfo {
+        icon_dataurl: "".to_string(),
+        app_name: props.get("Name").unwrap_or_default().to_string(),
+        comment: props.get("Comment").unwrap_or_default().to_string(),
+        icon_name: props.get("Icon").unwrap_or_default().to_string(),
+    }
+}
+
+fn get_icon_as_png_dataurl(name: &str, size: i8) -> Result<(String, String)> {
+    unsafe {
+        if GTK_DEFAULT_THEME.is_none() {
+            let theme = gtk_icon_theme_get_default();
+            if theme.is_null() {
+                debug!("You have to initialize GTK!");
+                return Err(Error::new(ErrorKind::Other, "You have to initialize GTK!").into());
+            }
+
+            let theme = gtk_icon_theme_get_default();
+            GTK_DEFAULT_THEME = Some(theme);
+        }
+    }
+
+    let mut icons = Vec::new();
+
+    // push the name
+    icons.push(name);
+
+    // if we don't find the icon by it's name and it includes an extension,
+    // drop the extension and try without.
+    let name_without_ext;
+    if let Some(ext) = PathBuf::from(name).extension() {
+        let ext = ext.to_str().unwrap();
+
+        let mut ext_dot = String::from(".").to_owned();
+        ext_dot.push_str(ext);
+
+        name_without_ext = name.replace(ext_dot.as_str(), "");
+        icons.push(name_without_ext.as_str());
+    } else {
+        name_without_ext = String::from(name);
+    }
+
+    // The xdg-desktop icon specification allows a fallback for icons that contains dashes.
+    // i.e. the following lookup order is used:
+    //      - network-wired-secure
+    //      - network-wired
+    //      - network
+    //
+    name_without_ext
+        .split("-")
+        .for_each(|part| icons.push(part));
+
+    for name in icons {
+        debug!("trying to load icon {}", name);
+
+        unsafe {
+            let c_str = CString::new(name).unwrap();
+
+            let icon_info = gtk_icon_theme_lookup_icon(
+                GTK_DEFAULT_THEME.unwrap(),
+                c_str.as_ptr() as *const i8,
+                size as c_int,
+                0,
+            );
+            if icon_info.is_null() {
+                error!("failed to lookup icon {}", name);
+
+                continue;
+            }
+
+            let filename = gtk_icon_info_get_filename(icon_info);
+
+            let filename = CStr::from_ptr(filename).to_str().unwrap().to_string();
+
+            gtk_icon_info_free(icon_info);
+
+            match read_and_convert_pixbuf(filename.clone()) {
+                Ok(pb) => return Ok((filename, pb)),
+                Err(err) => {
+                    error!("failed to load icon from {}: {}", filename, err.to_string());
+
+                    continue;
+                }
+            }
+        }
+    }
+
+    Err(Error::new(ErrorKind::NotFound, "failed to find icon").into())
+}
+
+/*
+fn get_icon_as_file_2(ext: &str, size: i32) -> io::Result<(String, Vec<u8>)> {
+    let result: String;
+    let buf: Vec<u8>;
+
+    unsafe {
+        let filename = CString::new(ext).unwrap();
+        let null: u8 = 0;
+        let p_null = &null as *const u8;
+        let nullsize: usize = 0;
+        let mut res = 0;
+        let p_res = &mut res as *mut i32;
+        let p_res = gio_sys::g_content_type_guess(filename.as_ptr(), p_null, nullsize, p_res);
+        let icon = gio_sys::g_content_type_get_icon(p_res);
+        g_free(p_res as *mut c_void);
+        if DEFAULT_THEME.is_none() {
+            let theme = gtk_icon_theme_get_default();
+            if theme.is_null() {
+                println!("You have to initialize GTK!");
+                return Err(io::Error::new(io::ErrorKind::Other, "You have to initialize GTK!"))
+            }
+            let theme = gtk_icon_theme_get_default();
+            DEFAULT_THEME = Some(theme);
+        }
+        let icon_names = gio_sys::g_themed_icon_get_names(icon as *mut GThemedIcon) as *mut *const i8;
+        let icon_info = gtk_icon_theme_choose_icon(DEFAULT_THEME.unwrap(), icon_names, size, GTK_ICON_LOOKUP_NO_SVG);
+        let filename = gtk_icon_info_get_filename(icon_info);
+
+        gtk_icon_info_free(icon_info);
+
+        result = CStr::from_ptr(filename).to_str().unwrap().to_string();
+
+        buf = match read_and_convert_pixbuf(result.clone()) {
+            Ok(pb) => pb,
+            Err(_) => Vec::new(),
+        };
+
+        g_object_unref(icon as *mut GObject);
+    }
+
+    Ok((result, buf))
+
+}
+*/
+
+fn read_and_convert_pixbuf(result: String) -> std::result::Result<String, glib::Error> {
+    let pixbuf = match Pixbuf::from_file(result.clone()) {
+        Ok(data) => Ok(data),
+        Err(err) => {
+            error!("failed to load icon pixbuf: {}", err.to_string());
+
+            Pixbuf::from_resource(result.clone().as_str())
+        }
+    };
+
+    match pixbuf {
+        Ok(data) => match data.save_to_bufferv("png", &[]) {
+            Ok(data) => {
+                let mut du = DataUrl::new();
+
+                du.set_media_type(Some("image/png".to_string()));
+                du.set_data(&data);
+
+                Ok(du.to_string())
+            }
+            Err(err) => {
+                return Err(glib::Error::new(
+                    PixbufError::Failed,
+                    err.to_string().as_str(),
+                ));
+            }
+        },
+        Err(err) => Err(err),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ctor::ctor;
+    use log::warn;
+    use which::which;
+
+    // Use the ctor create to setup a global initializer before our tests are executed.
+    #[ctor]
+    fn init() {
+        // we need to initialize GTK before running our tests.
+        // This is only required when unit tests are executed as
+        // GTK will otherwise be initialize by Tauri.
+
+        gtk::init().expect("failed to initialize GTK for tests")
+    }
+
+    #[test]
+    fn test_find_info_success() {
+        // we expect at least one of the following binaries to be installed
+        // on a linux system
+        let test_binaries = vec![
+            "vim",             // vim is mostly bundled with a .desktop file
+            "blueman-manager", // blueman-manager is the default bluetooth manager on most DEs
+            "nautilus",        // nautlis: file-manager on GNOME DE
+            "thunar",          // thunar: file-manager on XFCE
+            "dolphin",         // dolphin: file-manager on KDE
+        ];
+
+        let mut bin_found = false;
+
+        for cmd in test_binaries {
+            match which(cmd) {
+                Ok(bin) => {
+                    bin_found = true;
+
+                    let bin = bin.to_string_lossy().to_string();
+
+                    let result = get_app_info(ProcessInfo {
+                        cmdline: cmd.to_string(),
+                        exec_path: bin.clone(),
+                        matching_path: bin.clone(),
+                        pid: 0,
+                    })
+                    .expect(
+                        format!(
+                            "expected to find app info for {} ({})",
+                            bin,
+                            cmd.to_string()
+                        )
+                        .as_str(),
+                    );
+
+                    let empty_string = String::from("");
+
+                    // just make sure all fields are populated
+                    assert_ne!(result.app_name, empty_string);
+                    assert_ne!(result.comment, empty_string);
+                    assert_ne!(result.icon_name, empty_string);
+                    assert_ne!(result.icon_dataurl, empty_string);
+                }
+                Err(_) => {
+                    // binary not found
+                    continue;
+                }
+            }
+        }
+
+        if !bin_found {
+            warn!("test_find_info_success: no test binary found, test was skipped")
+        }
+    }
+}
diff --git a/desktop/tauri/src-tauri/tauri.conf.json b/desktop/tauri/src-tauri/tauri.conf.json
new file mode 100644
index 00000000..0fca6f4f
--- /dev/null
+++ b/desktop/tauri/src-tauri/tauri.conf.json
@@ -0,0 +1,106 @@
+{
+  "build": {
+    "beforeDevCommand": {
+      "script": "npm run tauri-dev",
+      "cwd": "../../angular",
+      "wait": false
+    },
+    "devPath": "http://localhost:4100",
+    "distDir": "../../angular/dist/tauri-builtin",
+    "withGlobalTauri": true
+  },
+  "package": {
+    "productName": "Portmaster",
+    "version": "0.1.0"
+  },
+  "plugins": {
+    "cli": {
+      "args": [
+        {
+          "short": "d",
+          "name": "data",
+          "description": "Path to the installation directory",
+          "takesValue": true
+        },
+        {
+          "short": "b",
+          "name": "background",
+          "description": "Start in the background without opening a window"
+        },
+        {
+          "name": "with-notifications",
+          "description": "Enable experimental notifications via Tauri. Replaces the notifier app."
+        },
+        {
+          "name": "with-prompts",
+          "description": "Enable experimental prompt support via Tauri. Replaces the notifier app."
+        }
+      ]
+    }
+  },
+  "tauri": {
+    "bundle": {
+      "active": true,
+      "category": "Utility",
+      "copyright": "",
+      "deb": {
+        "depends": []
+      },
+      "externalBin": [
+        "binaries/portmaster-start",
+        "binaries/portmaster-core"
+      ],
+      "icon": [
+        "../assets/icons/pm_dark_512.png",
+        "../assets/icons/pm_dark_512.ico",
+        "../assets/icons/pm_light_512.png",
+        "../assets/icons/pm_light_512.ico"
+      ],
+      "identifier": "io.safing.portmaster",
+      "longDescription": "",
+      "macOS": {
+        "entitlements": null,
+        "exceptionDomain": "",
+        "frameworks": [],
+        "providerShortName": null,
+        "signingIdentity": null
+      },
+      "resources": [],
+      "shortDescription": "",
+      "targets": [
+        "deb",
+        "appimage",
+        "nsis",
+        "msi",
+        "app"
+      ],
+      "windows": {
+        "certificateThumbprint": null,
+        "digestAlgorithm": "sha256",
+        "timestampUrl": ""
+      }
+    },
+    "security": {
+      "csp": null,
+      "dangerousRemoteDomainIpcAccess": [
+        {
+          "windows": [
+            "main",
+            "prompt"
+          ],
+          "plugins": [
+            "shell",
+            "os",
+            "clipboard-manager",
+            "event",
+            "window",
+            "cli",
+            "portmaster"
+          ],
+          "domain": "localhost"
+        }
+      ]
+    },
+    "windows": []
+  }
+}
\ No newline at end of file