Merge pull request #1519 from safing/feature/tauri-beta-update
Feature Tauri beta update
2
.github/workflows/angular.yml
vendored
|
@ -56,4 +56,4 @@ jobs:
|
|||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build angular projects
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +angular-release
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +angular-ci
|
||||
|
|
2
.github/workflows/kext.yml
vendored
|
@ -33,4 +33,4 @@ jobs:
|
|||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Kernel Extension
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-build
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-ci
|
||||
|
|
2
.github/workflows/tauri.yml
vendored
|
@ -33,4 +33,4 @@ jobs:
|
|||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build tauri project
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +tauri-release
|
||||
run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +tauri-ci
|
||||
|
|
388
Earthfile
|
@ -12,24 +12,90 @@ ARG --global work_image = "alpine"
|
|||
|
||||
ARG --global outputDir = "./dist"
|
||||
|
||||
# Architectures:
|
||||
# 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" \
|
||||
"x86_64-pc-windows-gnu"
|
||||
# TODO: Compile errors here:
|
||||
# "aarch64-pc-windows-gnu" \
|
||||
# "x86_64-apple-darwin" \
|
||||
# "aarch64-apple-darwin"
|
||||
# "armv7-unknown-linux-gnueabihf" \
|
||||
# "arm-unknown-linux-gnueabi"
|
||||
#
|
||||
# Linux:
|
||||
# x86_64-unknown-linux-gnu
|
||||
# aarch64-unknown-linux-gnu
|
||||
# armv7-unknown-linux-gnueabihf
|
||||
# arm-unknown-linux-gnueabi
|
||||
#
|
||||
# Windows:
|
||||
# x86_64-pc-windows-gnu
|
||||
# aarch64-pc-windows-gnu
|
||||
#
|
||||
# Mac:
|
||||
# x86_64-apple-darwin
|
||||
# aarch64-apple-darwin
|
||||
|
||||
# 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
|
||||
|
||||
build:
|
||||
# Build all Golang binaries:
|
||||
# ./dist/linux_amd64/portmaster-core
|
||||
# ./dist/linux_amd64/portmaster-start
|
||||
# ./dist/linux_arm64/portmaster-core
|
||||
# ./dist/linux_arm64/portmaster-start
|
||||
# ./dist/windows_amd64/portmaster-core.exe
|
||||
# ./dist/windows_amd64/portmaster-start.exe
|
||||
# ./dist/windows_arm64/portmaster-core.exe
|
||||
# ./dist/windows_arm64/portmaster-start.exe
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="amd64"
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="arm64"
|
||||
BUILD +go-build --GOOS="windows" --GOARCH="amd64"
|
||||
BUILD +go-build --GOOS="windows" --GOARCH="arm64"
|
||||
|
||||
# Build the Angular UI:
|
||||
# ./dist/all/portmaster-ui.zip
|
||||
BUILD +angular-release
|
||||
|
||||
# Build Tauri app binaries:
|
||||
# ./dist/linux_amd64/portmaster-app
|
||||
# ./dist/linux_amd64/Portmaster-0.1.0-1.x86_64.rpm
|
||||
# ./dist/linux_amd64/Portmaster_0.1.0_amd64.deb
|
||||
BUILD +tauri-build --target="x86_64-unknown-linux-gnu"
|
||||
# TODO:
|
||||
# BUILD +tauri-build --target="x86_64-pc-windows-gnu"
|
||||
|
||||
# Bild Tauri release bundle for Windows:
|
||||
# ./dist/windows_amd64/portmaster-app_vX-X-X.zip
|
||||
BUILD +tauri-windows-release-bundle
|
||||
|
||||
# Build UI assets:
|
||||
# ./dist/all/assets.zip
|
||||
BUILD +assets
|
||||
|
||||
go-ci:
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="amd64"
|
||||
BUILD +go-build --GOOS="linux" --GOARCH="arm64"
|
||||
BUILD +go-build --GOOS="windows" --GOARCH="amd64"
|
||||
BUILD +go-build --GOOS="windows" --GOARCH="arm64"
|
||||
BUILD +go-test
|
||||
|
||||
angular-ci:
|
||||
BUILD +angular-release
|
||||
|
||||
tauri-ci:
|
||||
BUILD +tauri-build --target="x86_64-unknown-linux-gnu"
|
||||
BUILD +tauri-windows-release-bundle
|
||||
|
||||
kext-ci:
|
||||
BUILD +kext-build
|
||||
|
||||
release:
|
||||
LOCALLY
|
||||
|
||||
IF ! git diff --quiet
|
||||
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
|
||||
END
|
||||
|
||||
BUILD +build
|
||||
|
||||
go-deps:
|
||||
FROM ${go_builder_image}
|
||||
WORKDIR /go-workdir
|
||||
|
@ -111,7 +177,7 @@ go-build:
|
|||
ARG GOOS=linux
|
||||
ARG GOARCH=amd64
|
||||
ARG GOARM
|
||||
ARG CMDS=portmaster-start portmaster-core hub notifier
|
||||
ARG CMDS=portmaster-start portmaster-core
|
||||
|
||||
CACHE --sharing shared "$GOCACHE"
|
||||
CACHE --sharing shared "$GOMODCACHE"
|
||||
|
@ -182,6 +248,7 @@ go-test:
|
|||
|
||||
go-test-all:
|
||||
FROM ${work_image}
|
||||
ARG --required architectures
|
||||
|
||||
FOR arch IN ${architectures}
|
||||
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
|
||||
|
@ -197,6 +264,7 @@ go-lint:
|
|||
# Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms
|
||||
go-release:
|
||||
FROM ${work_image}
|
||||
ARG --required architectures
|
||||
|
||||
FOR arch IN ${architectures}
|
||||
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
|
||||
|
@ -218,11 +286,6 @@ go-build-utils:
|
|||
BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=linux
|
||||
BUILD +go-build --CMDS="" --GOARCH=amd64 --GOOS=windows
|
||||
|
||||
# All targets that should run in CI for go.
|
||||
go-ci:
|
||||
BUILD +go-release
|
||||
BUILD +go-test
|
||||
|
||||
# Prepares the angular project by installing dependencies
|
||||
angular-deps:
|
||||
FROM ${node_builder_image}
|
||||
|
@ -272,7 +335,7 @@ angular-project:
|
|||
|
||||
RUN --no-cache cwd=$(pwd) && cd "${dist}" && zip -r "${cwd}/${project}.zip" ./
|
||||
SAVE ARTIFACT --keep-ts "${dist}" "./output/${project}"
|
||||
|
||||
|
||||
# Save portmaster UI as local artifact.
|
||||
IF [ "${project}" = "portmaster" ]
|
||||
SAVE ARTIFACT --keep-ts "./${project}.zip" AS LOCAL ${outputDir}/all/${project}-ui.zip
|
||||
|
@ -302,9 +365,6 @@ assets:
|
|||
rust-base:
|
||||
FROM ${rust_builder_image}
|
||||
|
||||
RUN dpkg --add-architecture armhf
|
||||
RUN dpkg --add-architecture arm64
|
||||
|
||||
RUN apt-get update -qq
|
||||
|
||||
# Tools and libraries required for cross-compilation
|
||||
|
@ -318,74 +378,39 @@ rust-base:
|
|||
gcc-multilib \
|
||||
linux-libc-dev \
|
||||
linux-libc-dev-amd64-cross \
|
||||
linux-libc-dev-arm64-cross \
|
||||
linux-libc-dev-armel-cross \
|
||||
linux-libc-dev-armhf-cross \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libsoup-3.0-dev \
|
||||
libwebkit2gtk-4.1-dev
|
||||
libwebkit2gtk-4.1-dev \
|
||||
gcc-mingw-w64-x86-64 \
|
||||
zip
|
||||
|
||||
# Install library dependencies for all supported architectures
|
||||
# required for succesfully linking.
|
||||
FOR arch IN amd64 arm64 armhf
|
||||
RUN apt-get install --no-install-recommends -qq \
|
||||
libsoup-3.0-0:${arch} \
|
||||
libwebkit2gtk-4.1-0:${arch} \
|
||||
libssl3:${arch} \
|
||||
libayatana-appindicator3-1:${arch} \
|
||||
librsvg2-bin:${arch} \
|
||||
libgtk-3-0:${arch} \
|
||||
libjavascriptcoregtk-4.1-0:${arch} \
|
||||
libssl-dev:${arch} \
|
||||
libayatana-appindicator3-dev:${arch} \
|
||||
librsvg2-dev:${arch} \
|
||||
libgtk-3-dev:${arch} \
|
||||
libjavascriptcoregtk-4.1-dev:${arch}
|
||||
END
|
||||
|
||||
# Note(ppacher): I've no idea why we need to explicitly create those symlinks:
|
||||
# Some how all the other libs work but libsoup and libwebkit2gtk do not create the link file
|
||||
RUN cd /usr/lib/aarch64-linux-gnu && \
|
||||
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
|
||||
ln -s libsoup-3.0.so.0 libsoup-3.0.so
|
||||
|
||||
RUN cd /usr/lib/arm-linux-gnueabihf && \
|
||||
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
|
||||
ln -s libsoup-3.0.so.0 libsoup-3.0.so
|
||||
|
||||
# For what ever reason trying to install the gcc compilers together with the above
|
||||
# command makes apt fail due to conflicts with gcc-multilib. Installing in a separate
|
||||
# step seems to work ...
|
||||
RUN apt-get install --no-install-recommends -qq \
|
||||
g++-mingw-w64-x86-64 \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-none-eabi \
|
||||
gcc-arm-linux-gnueabi \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-arm64-cross \
|
||||
libc6-dev-armel-cross \
|
||||
libc6-dev-armhf-cross \
|
||||
libc6-dev-amd64-cross
|
||||
libsoup-3.0-0 \
|
||||
libwebkit2gtk-4.1-0 \
|
||||
libssl3 \
|
||||
libayatana-appindicator3-1 \
|
||||
librsvg2-bin \
|
||||
libgtk-3-0 \
|
||||
libjavascriptcoregtk-4.1-0 \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
libgtk-3-dev \
|
||||
libjavascriptcoregtk-4.1-dev
|
||||
|
||||
# Add some required rustup components
|
||||
RUN rustup component add clippy
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
# Install architecture 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"
|
||||
|
||||
# Required for cross compilation to work.
|
||||
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||
# For now we need tauri-cli 2.0.0 for bulding
|
||||
DO rust+CARGO --args="install tauri-cli --version ^2.0.0-beta"
|
||||
|
||||
# Explicitly cache here.
|
||||
SAVE IMAGE --cache-hint
|
||||
|
@ -398,7 +423,7 @@ tauri-src:
|
|||
# --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 assets/data ./../../assets/data
|
||||
COPY packaging/linux ./../../packaging/linux
|
||||
COPY (+angular-project/output/tauri-builtin --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/") ./../angular/dist/tauri-builtin
|
||||
|
||||
|
@ -411,13 +436,151 @@ tauri-build:
|
|||
FROM +tauri-src
|
||||
|
||||
ARG --required target
|
||||
ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe)))|bundle/.*\.(deb|msi|AppImage))"
|
||||
ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe)))|bundle/(deb|rpm)/.*\.(deb|rpm))"
|
||||
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
|
||||
# We already know 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.
|
||||
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}"
|
||||
|
||||
# Our tauri app has externalBins configured so tauri will try to embed them when it finished compiling
|
||||
# the app. Make sure we copy portmaster-start and portmaster-core in all architectures supported.
|
||||
# See documentation for externalBins for more information on how tauri searches for the binaries.
|
||||
COPY (+go-build/output --CMDS="portmaster-start portmaster-core" --GOOS="${GOOS}" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
|
||||
|
||||
# Place them in the correct folder with the rust target tripple attached.
|
||||
FOR bin IN $(ls /tmp/gobuild)
|
||||
# ${bin$.*} does not work in SET commands unfortunately so we use a shell
|
||||
# snippet here:
|
||||
RUN set -e ; \
|
||||
dest="./binaries/${bin}-${target}" ; \
|
||||
if [ -z "${bin##*.exe}" ]; then \
|
||||
dest="./binaries/${bin%.*}-${target}.exe" ; \
|
||||
fi ; \
|
||||
cp "/tmp/gobuild/${bin}" "${dest}" ;
|
||||
END
|
||||
|
||||
# Just for debugging ...
|
||||
# RUN ls -R ./binaries
|
||||
|
||||
# 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 rustup target add "${target}"
|
||||
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --ci --target="${target}"
|
||||
DO rust+COPY_OUTPUT --output="${output}"
|
||||
|
||||
# BUG(cross-compilation):
|
||||
#
|
||||
# The above command seems to correctly compile for all architectures we want to support but fails during
|
||||
# linking since the target libaries are not available for the requested platforms. Maybe we need to download
|
||||
# the, manually ...
|
||||
#
|
||||
# The earthly rust lib also has support for using cross-rs for cross-compilation but that fails due to the
|
||||
# fact that cross-rs base docker images used for building are heavily outdated (latest = ubunut:16.0, main = ubuntu:20.04)
|
||||
# which does not ship recent enough glib versions (our glib dependency needs glib>2.70 but ubunut:20.04 only ships 2.64)
|
||||
#
|
||||
# The following would use the CROSS function from the earthly lib, this
|
||||
# DO rust+CROSS --target="${target}"
|
||||
|
||||
RUN echo output: $(ls -R "target/${target}/release")
|
||||
LET outbin="error"
|
||||
FOR bin IN "portmaster Portmaster.exe WebView2Loader.dll"
|
||||
# Modify output binary.
|
||||
SET outbin="${bin}"
|
||||
IF [ "${bin}" = "portmaster" ]
|
||||
SET outbin="portmaster-app"
|
||||
ELSE IF [ "${bin}" = "Portmaster.exe" ]
|
||||
SET outbin="portmaster-app.exe"
|
||||
END
|
||||
# Save output binary as local artifact.
|
||||
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${outbin}"
|
||||
END
|
||||
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/bundle/deb/*.deb" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
|
||||
SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/bundle/rpm/*.rpm" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
|
||||
|
||||
tauri-windows-release-bundle:
|
||||
FROM +tauri-src
|
||||
|
||||
ARG target="x86_64-pc-windows-gnu"
|
||||
ARG output=".*/release/(([^\./]+|([^\./]+\.(dll|exe))))"
|
||||
ARG bundle="none"
|
||||
|
||||
ARG GOOS=windows
|
||||
ARG GOARCH=amd64
|
||||
ARG GOARM
|
||||
|
||||
# The binaries will not be used but we still need to create them. Tauri will check for them.
|
||||
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}"
|
||||
|
||||
# Our tauri app has externalBins configured so tauri will look for them when it finished compiling
|
||||
# the app. Make sure we copy portmaster-start and portmaster-core in all architectures supported.
|
||||
# See documentation for externalBins for more information on how tauri searches for the binaries.
|
||||
COPY (+go-build/output --GOOS="${GOOS}" --CMDS="portmaster-start portmaster-core" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
|
||||
|
||||
# Place them in the correct folder with the rust target tripple attached.
|
||||
FOR bin IN $(ls /tmp/gobuild)
|
||||
# ${bin$.*} does not work in SET commands unfortunately so we use a shell
|
||||
# snippet here:
|
||||
RUN set -e ; \
|
||||
dest="./binaries/${bin}-${target}" ; \
|
||||
if [ -z "${bin##*.exe}" ]; then \
|
||||
dest="./binaries/${bin%.*}-${target}.exe" ; \
|
||||
fi ; \
|
||||
cp "/tmp/gobuild/${bin}" "${dest}" ;
|
||||
END
|
||||
|
||||
# Just for debugging ...
|
||||
# RUN ls -R ./binaries
|
||||
|
||||
DO rust+SET_CACHE_MOUNTS_ENV
|
||||
RUN rustup target add "${target}"
|
||||
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --no-bundle --ci --target="${target}"
|
||||
DO rust+COPY_OUTPUT --output="${output}"
|
||||
|
||||
# Get version from git.
|
||||
COPY .git .
|
||||
LET version = "$(git tag --points-at || true)"
|
||||
IF [ -z "${version}" ]
|
||||
LET dev_version = "$(git describe --tags --first-parent --abbrev=0 || true)"
|
||||
IF [ -n "${dev_version}" ]
|
||||
SET version = "${dev_version}"
|
||||
END
|
||||
END
|
||||
IF [ -z "${version}" ]
|
||||
SET version = "v0.0.0"
|
||||
END
|
||||
ENV VERSION="${version}"
|
||||
RUN echo "Version: $VERSION"
|
||||
ENV VERSION_SUFFIX="$(echo $VERSION | tr '.' '-')"
|
||||
RUN echo "Version Suffix: $VERSION_SUFFIX"
|
||||
|
||||
RUN echo output: $(ls -R "target/${target}/release")
|
||||
RUN mv "target/${target}/release/app.exe" "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.exe"
|
||||
RUN zip "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.zip" "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.exe" -j portmaster-app${VERSION_SUFFIX}.exe "target/${target}/release/WebView2Loader.dll" -j WebView2Loader.dll
|
||||
SAVE ARTIFACT --if-exists "target/${target}/release/portmaster-app_${VERSION_SUFFIX}.zip" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/"
|
||||
|
||||
tauri-prep-windows:
|
||||
FROM +angular-base --configuration=production
|
||||
ARG target="x86_64-pc-windows-msvc"
|
||||
|
||||
# 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 know 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.
|
||||
RUN mkdir /tmp/gobuild
|
||||
RUN mkdir ./binaries
|
||||
|
@ -443,55 +606,29 @@ tauri-build:
|
|||
cp "/tmp/gobuild/${bin}" "${dest}" ;
|
||||
END
|
||||
|
||||
# Copy source
|
||||
COPY --keep-ts ./desktop/tauri/src-tauri src-tauri
|
||||
COPY --keep-ts ./assets assets
|
||||
|
||||
# Build UI
|
||||
ENV NODE_ENV="production"
|
||||
RUN --no-cache ./node_modules/.bin/ng build --configuration production --base-href / "tauri-builtin"
|
||||
|
||||
# Just for debugging ...
|
||||
RUN ls -R ./binaries
|
||||
# RUN ls -R ./binaries
|
||||
# RUN ls -R ./dist
|
||||
|
||||
# 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}"
|
||||
|
||||
# BUG(cross-compilation):
|
||||
#
|
||||
# The above command seems to correctly compile for all architectures we want to support but fails during
|
||||
# linking since the target libaries are not available for the requested platforms. Maybe we need to download
|
||||
# the, manually ...
|
||||
#
|
||||
# The earthly rust lib also has support for using cross-rs for cross-compilation but that fails due to the
|
||||
# fact that cross-rs base docker images used for building are heavily outdated (latest = ubunut:16.0, main = ubuntu:20.04)
|
||||
# which does not ship recent enough glib versions (our glib dependency needs glib>2.70 but ubunut:20.04 only ships 2.64)
|
||||
#
|
||||
# The following would use the CROSS function from the earthly lib, this
|
||||
# DO rust+CROSS --target="${target}"
|
||||
|
||||
# RUN echo output: $(ls "target/${target}/release")
|
||||
LET outbin="error"
|
||||
FOR bin IN "portmaster Portmaster.exe WebView2Loader.dll"
|
||||
# Modify output binary.
|
||||
SET outbin="${bin}"
|
||||
IF [ "${bin}" = "portmaster" ]
|
||||
SET outbin="portmaster-app"
|
||||
ELSE IF [ "${bin}" = "Portmaster.exe" ]
|
||||
SET outbin="portmaster-app.exe"
|
||||
END
|
||||
# Save output binary as local artifact.
|
||||
IF [ -f "target/${target}/release/${bin}" ]
|
||||
SAVE ARTIFACT --keep-ts "target/${target}/release/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${outbin}"
|
||||
END
|
||||
END
|
||||
SAVE ARTIFACT "./dist/tauri-builtin" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/angular/dist/"
|
||||
SAVE ARTIFACT "./src-tauri" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/tauri/src-tauri"
|
||||
SAVE ARTIFACT "./binaries" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/desktop/tauri/src-tauri/"
|
||||
SAVE ARTIFACT "./assets" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/assets"
|
||||
|
||||
tauri-release:
|
||||
FROM ${work_image}
|
||||
|
||||
ARG bundle="none"
|
||||
ARG --required architectures
|
||||
|
||||
FOR arch IN ${architectures}
|
||||
BUILD +tauri-build --target="${arch}" --bundle="${bundle}"
|
||||
BUILD +tauri-build --target="${arch}"
|
||||
END
|
||||
|
||||
kext-build:
|
||||
|
@ -515,21 +652,6 @@ kext-build:
|
|||
|
||||
SAVE ARTIFACT --keep-ts "portmaster-kext-release-bundle.zip" AS LOCAL "${outputDir}/windows_amd64/portmaster-kext-release-bundle.zip"
|
||||
|
||||
build:
|
||||
BUILD +go-release
|
||||
BUILD +angular-release
|
||||
BUILD +tauri-release
|
||||
BUILD +assets
|
||||
|
||||
release:
|
||||
LOCALLY
|
||||
|
||||
IF ! git diff --quiet
|
||||
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
|
||||
END
|
||||
|
||||
BUILD +build
|
||||
|
||||
|
||||
# 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}.
|
||||
|
|
14
assets/data/icons/generate_ico.sh
Normal file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Traymenu icons. Sometimes to wrong size is selected so remove 256 and 64.
|
||||
convert pm_dark_green_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_green.ico
|
||||
convert pm_dark_blue_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_blue.ico
|
||||
convert pm_dark_red_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_red.ico
|
||||
convert pm_dark_yellow_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_dark_yellow.ico
|
||||
convert pm_light_blue_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_blue.ico
|
||||
convert pm_light_green_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_green.ico
|
||||
convert pm_light_red_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_red.ico
|
||||
convert pm_light_yellow_512.png -colors 256 -define icon:auto-resize=48,32,16 pm_light_yellow.ico
|
||||
|
||||
convert pm_dark_512.png -colors 256 -define icon:auto-resize=64,48,32,16 pm_dark.ico
|
||||
convert pm_light_512.png -colors 256 -define icon:auto-resize=64,48,32,16 pm_light.ico
|
BIN
assets/data/icons/pm_dark.ico
Normal file
After (image error) Size: 31 KiB |
Before (image error) Size: 108 KiB |
BIN
assets/data/icons/pm_dark_blue.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 97 KiB |
BIN
assets/data/icons/pm_dark_green.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
BIN
assets/data/icons/pm_dark_red.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
BIN
assets/data/icons/pm_dark_yellow.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 109 KiB |
BIN
assets/data/icons/pm_light.ico
Normal file
After (image error) Size: 31 KiB |
Before (image error) Size: 108 KiB |
BIN
assets/data/icons/pm_light_blue.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
BIN
assets/data/icons/pm_light_green.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
BIN
assets/data/icons/pm_light_red.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
BIN
assets/data/icons/pm_light_yellow.ico
Normal file
After (image error) Size: 15 KiB |
Before (image error) Size: 110 KiB |
|
@ -14,16 +14,16 @@ const (
|
|||
|
||||
// Icons.
|
||||
var (
|
||||
//go:embed data/icons/pm_light_green_512.ico
|
||||
//go:embed data/icons/pm_light_green_256.png
|
||||
GreenICO []byte
|
||||
|
||||
//go:embed data/icons/pm_light_yellow_512.ico
|
||||
//go:embed data/icons/pm_light_yellow_256.png
|
||||
YellowICO []byte
|
||||
|
||||
//go:embed data/icons/pm_light_red_512.ico
|
||||
//go:embed data/icons/pm_light_red_256.png
|
||||
RedICO []byte
|
||||
|
||||
//go:embed data/icons/pm_light_blue_512.ico
|
||||
//go:embed data/icons/pm_light_blue_256.png
|
||||
BlueICO []byte
|
||||
|
||||
// ColoredIcons holds all the icons as .ICOs
|
||||
|
|
|
@ -28,6 +28,7 @@ var (
|
|||
maxRetries int
|
||||
dataRoot *utils.DirStructure
|
||||
logsRoot *utils.DirStructure
|
||||
forceOldUI bool
|
||||
|
||||
updateURLFlag string
|
||||
userAgentFlag string
|
||||
|
@ -74,6 +75,7 @@ func init() {
|
|||
flags.StringVar(&userAgentFlag, "update-agent", "", "Set an alternative user agent for requests to the update server")
|
||||
flags.IntVar(&maxRetries, "max-retries", 5, "Maximum number of retries when starting a Portmaster component")
|
||||
flags.BoolVar(&stdinSignals, "input-signals", false, "Emulate signals using stdin.")
|
||||
flags.BoolVar(&forceOldUI, "old-ui", false, "Use the old ui. (Beta)")
|
||||
_ = rootCmd.MarkPersistentFlagDirname("data")
|
||||
_ = flags.MarkHidden("input-signals")
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ const (
|
|||
// This disables retrying and exits with an error code.
|
||||
ControlledFailureExitCode = 24
|
||||
|
||||
// StartOldUIExitCode is an exit code that is returned by the UI when there. This is manfully triaged by the user, if the new UI does not work for them.
|
||||
StartOldUIExitCode = 77
|
||||
MissingDependencyExitCode = 0xc0000135 // Windows STATUS_DLL_NOT_FOUND
|
||||
|
||||
exeSuffix = ".exe"
|
||||
zipSuffix = ".zip"
|
||||
)
|
||||
|
@ -38,6 +42,8 @@ var (
|
|||
onWindows = runtime.GOOS == "windows"
|
||||
stdinSignals bool
|
||||
childIsRunning = abool.NewBool(false)
|
||||
|
||||
fallBackToOldUI bool = false
|
||||
)
|
||||
|
||||
// Options for starting component.
|
||||
|
@ -55,7 +61,21 @@ type Options struct {
|
|||
RestartOnFail bool // Try restarting automatically, if the started component fails.
|
||||
}
|
||||
|
||||
// This is a temp value that will be used to test the new UI in beta.
|
||||
var app2Options = Options{
|
||||
Name: "Portmaster App2",
|
||||
Identifier: "app2/portmaster-app",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
RestartOnFail: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Make sure the new UI has a proper extension.
|
||||
if onWindows {
|
||||
app2Options.Identifier += ".zip"
|
||||
}
|
||||
|
||||
registerComponent([]Options{
|
||||
{
|
||||
Name: "Portmaster Core",
|
||||
|
@ -70,6 +90,7 @@ func init() {
|
|||
Identifier: "app/portmaster-app.zip",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
RestartOnFail: true,
|
||||
},
|
||||
{
|
||||
Name: "Portmaster Notifier",
|
||||
|
@ -89,6 +110,26 @@ func init() {
|
|||
RestartOnFail: true,
|
||||
},
|
||||
})
|
||||
|
||||
if onWindows {
|
||||
registerComponent([]Options{
|
||||
{
|
||||
Name: "Portmaster App2",
|
||||
Identifier: "app2/portmaster-app.zip",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
registerComponent([]Options{
|
||||
{
|
||||
Name: "Portmaster App2",
|
||||
Identifier: "app2/portmaster-app",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func registerComponent(opts []Options) {
|
||||
|
@ -311,6 +352,14 @@ func persistOutputStreams(opts *Options, version string, cmd *exec.Cmd) (chan st
|
|||
}
|
||||
|
||||
func execute(opts *Options, args []string) (cont bool, err error) {
|
||||
if !forceOldUI && registry.UsePreReleases && opts.ShortIdentifier == "app" {
|
||||
// Check if new ui was already tried.
|
||||
if !fallBackToOldUI {
|
||||
opts = &app2Options
|
||||
log.Println("Using new UI")
|
||||
}
|
||||
}
|
||||
|
||||
file, err := registry.GetFile(
|
||||
helper.PlatformIdentifier(opts.Identifier),
|
||||
)
|
||||
|
@ -440,6 +489,12 @@ func parseExitError(err error) (restart bool, errWithCtx error) {
|
|||
return true, nil
|
||||
case ControlledFailureExitCode:
|
||||
return false, errors.New("controlled failure, check logs")
|
||||
case StartOldUIExitCode:
|
||||
fallBackToOldUI = true
|
||||
return true, errors.New("user requested old UI")
|
||||
case MissingDependencyExitCode:
|
||||
fallBackToOldUI = true
|
||||
return true, errors.New("new UI failed with missing dependency")
|
||||
default:
|
||||
return true, fmt.Errorf("unknown exit code %w", exErr)
|
||||
}
|
||||
|
|
9035
desktop/angular/package-lock.json
generated
|
@ -37,13 +37,13 @@
|
|||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@tauri-apps/api": "^2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-cli": "^2.0.0-beta.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-alpha.4",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-alpha.4",
|
||||
"@tauri-apps/plugin-notification": "^2.0.0-alpha.4",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-alpha.5",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-alpha.4",
|
||||
"@tauri-apps/api": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-cli": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-dialog": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-notification": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-os": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"d3": "^7.8.4",
|
||||
"data-urls": "^5.0.0",
|
||||
|
@ -101,4 +101,4 @@
|
|||
"webpack-ext-reloader": "^1.1.9",
|
||||
"zip-a-folder": "^1.1.5"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ function asyncInvoke<T>(method: string, args: object): Promise<T> {
|
|||
if (typeof event.payload === 'object' && 'error' in event.payload) {
|
||||
reject(event.payload);
|
||||
return
|
||||
};
|
||||
}
|
||||
|
||||
resolve(event.payload);
|
||||
})
|
||||
|
@ -75,7 +75,7 @@ export class TauriIntegrationService implements IntegrationService {
|
|||
}
|
||||
|
||||
getAppInfo(info: ProcessInfo): Promise<AppInfo> {
|
||||
return asyncInvoke("plugin:portmaster|get_app_info", {
|
||||
return asyncInvoke("get_app_info", {
|
||||
...info,
|
||||
})
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ export class TauriIntegrationService implements IntegrationService {
|
|||
|
||||
async shouldShow(): Promise<boolean> {
|
||||
try {
|
||||
const response = await invoke<string>("plugin:portmaster|should_show");
|
||||
const response = await invoke<string>("should_show");
|
||||
return response === "show";
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -122,7 +122,7 @@ export class TauriIntegrationService implements IntegrationService {
|
|||
|
||||
async shouldHandlePrompts(): Promise<boolean> {
|
||||
try {
|
||||
const response = await invoke<string>("plugin:portmaster|should_handle_prompts")
|
||||
const response = await invoke<string>("should_handle_prompts")
|
||||
return response === "true"
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -130,27 +130,27 @@ export class TauriIntegrationService implements IntegrationService {
|
|||
}
|
||||
}
|
||||
|
||||
get_state(key: string): Promise<string> {
|
||||
return invoke<string>("plugin:portmaster|get_state");
|
||||
get_state(_: string): Promise<string> {
|
||||
return invoke<string>("get_state");
|
||||
}
|
||||
|
||||
set_state(key: string, value: string): Promise<void> {
|
||||
return invoke<void>("plugin:portmaster|set_state", {
|
||||
return invoke<void>("set_state", {
|
||||
key,
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
getServiceManagerStatus(): Promise<ServiceManagerStatus> {
|
||||
return asyncInvoke("plugin:portmaster|get_service_manager_status", {})
|
||||
return asyncInvoke("get_service_manager_status", {})
|
||||
}
|
||||
|
||||
startService(): Promise<any> {
|
||||
return asyncInvoke("plugin:portmaster|start_service", {});
|
||||
return asyncInvoke("start_service", {});
|
||||
}
|
||||
|
||||
onExitRequest(cb: () => void): () => void {
|
||||
let unlisten: () => void = () => { };
|
||||
let unlisten: () => void = () => undefined;
|
||||
|
||||
listen('exit-requested', () => {
|
||||
cb();
|
||||
|
@ -189,7 +189,7 @@ export class TauriIntegrationService implements IntegrationService {
|
|||
return;
|
||||
}
|
||||
|
||||
let promptWindow = new Window("prompt", {
|
||||
const promptWindow = new Window("prompt", {
|
||||
alwaysOnTop: true,
|
||||
decorations: false,
|
||||
minimizable: false,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../assets/data
|
4992
desktop/tauri/src-tauri/Cargo.lock
generated
|
@ -12,21 +12,21 @@ 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 = [] }
|
||||
tauri-build = { version = "2.0.0-beta.18", 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"
|
||||
tauri = { version = "2.0.0-beta.23", features = ["tray-icon", "image-png", "config-json5"] }
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri-plugin-dialog = "2.0.0-beta"
|
||||
tauri-plugin-clipboard-manager = "2.0.0-beta"
|
||||
tauri-plugin-os = "2.0.0-beta"
|
||||
tauri-plugin-single-instance = "2.0.0-beta"
|
||||
tauri-plugin-cli = "2.0.0-beta"
|
||||
tauri-plugin-notification = "2.0.0-beta"
|
||||
tauri-plugin-log = "2.0.0-beta"
|
||||
|
||||
# We still need the tauri-cli 1.5 for building
|
||||
tauri-cli = "1.5.11"
|
||||
tauri-cli = "2.0.0-beta.21"
|
||||
|
||||
# General
|
||||
serde_json = "1.0"
|
||||
|
@ -47,7 +47,10 @@ http = "1.0.0"
|
|||
url = "2.5.0"
|
||||
thiserror = "1.0"
|
||||
log = "0.4.21"
|
||||
pretty_env_logger = "0.5.0"
|
||||
reqwest = { version = "0.12" }
|
||||
|
||||
rfd = { version = "*", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] }
|
||||
open = "5.1.3"
|
||||
|
||||
# Linux only
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
@ -62,6 +65,7 @@ gio-sys = "0.18.1"
|
|||
[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"] }
|
||||
tauri-winrt-notification = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
which = "6.0.0"
|
||||
|
|
32
desktop/tauri/src-tauri/capabilities/default.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": [
|
||||
"main",
|
||||
"splash"
|
||||
],
|
||||
"remote": {
|
||||
"urls": [
|
||||
"http://localhost:817"
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"path:default",
|
||||
"event:allow-listen",
|
||||
"event:allow-unlisten",
|
||||
"event:allow-emit",
|
||||
"event:allow-emit-to",
|
||||
"window:allow-hide",
|
||||
"window:allow-show",
|
||||
"window:allow-is-visible",
|
||||
"window:allow-set-focus",
|
||||
"app:default",
|
||||
"image:default",
|
||||
"resources:default",
|
||||
"menu:default",
|
||||
"tray:default",
|
||||
"shell:allow-open",
|
||||
"notification:default"
|
||||
]
|
||||
}
|
1
desktop/tauri/src-tauri/gen/schemas/acl-manifests.json
Normal file
1
desktop/tauri/src-tauri/gen/schemas/capabilities.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"default":{"identifier":"default","description":"Capability for the main window","remote":{"urls":["http://localhost:817"]},"local":true,"windows":["main","splash"],"permissions":["path:default","event:allow-listen","event:allow-unlisten","event:allow-emit","event:allow-emit-to","window:allow-hide","window:allow-show","window:allow-is-visible","window:allow-set-focus","app:default","image:default","resources:default","menu:default","tray:default","shell:allow-open","notification:default"]}}
|
2929
desktop/tauri/src-tauri/gen/schemas/desktop-schema.json
Normal file
2929
desktop/tauri/src-tauri/gen/schemas/linux-schema.json
Normal file
2929
desktop/tauri/src-tauri/gen/schemas/windows-schema.json
Normal file
|
@ -1,6 +1,8 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
||||
use tauri_plugin_cli::CliExt;
|
||||
|
||||
|
@ -16,7 +18,7 @@ mod portmaster;
|
|||
mod traymenu;
|
||||
mod window;
|
||||
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use log::{debug, error, info};
|
||||
use portmaster::PortmasterExt;
|
||||
use traymenu::setup_tray_menu;
|
||||
use window::{close_splash_window, create_main_window};
|
||||
|
@ -24,6 +26,8 @@ use window::{close_splash_window, create_main_window};
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
const FALLBACK_TO_OLD_UI_EXIT_CODE: i32 = 77;
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
args: Vec<String>,
|
||||
|
@ -82,12 +86,38 @@ impl portmaster::Handler for WsHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_webview_not_installed_dialog() -> i32 {
|
||||
use rfd::MessageDialog;
|
||||
|
||||
let result = MessageDialog::new()
|
||||
.set_title("Portmaster")
|
||||
.set_description("Webkit is not installed. Please install it and run portmaster again")
|
||||
.set_buttons(rfd::MessageButtons::OkCancelCustom(
|
||||
"Go to install page".to_owned(),
|
||||
"Use old UI".to_owned(),
|
||||
))
|
||||
.show();
|
||||
println!("{:?}", result);
|
||||
if let rfd::MessageDialogResult::Custom(result) = result {
|
||||
if result.eq("Go to install page") {
|
||||
_ = open::that("https://wiki.safing.io/en/Portmaster/Install/Webview");
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return FALLBACK_TO_OLD_UI_EXIT_CODE;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
if let Err(_) = tauri::webview_version() {
|
||||
std::process::exit(show_webview_not_installed_dialog());
|
||||
}
|
||||
|
||||
let app = tauri::Builder::default()
|
||||
// Shell plugin for open_external support
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_log::Builder::default().build())
|
||||
// Clipboard support
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
// Dialog (Save/Open) support
|
||||
|
@ -102,16 +132,24 @@ fn main() {
|
|||
.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())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
portmaster::commands::get_app_info,
|
||||
portmaster::commands::get_service_manager_status,
|
||||
portmaster::commands::start_service,
|
||||
portmaster::commands::get_state,
|
||||
portmaster::commands::set_state,
|
||||
portmaster::commands::should_show,
|
||||
portmaster::commands::should_handle_prompts
|
||||
])
|
||||
// Setup the app an any listeners
|
||||
.setup(|app| {
|
||||
setup_tray_menu(app)?;
|
||||
portmaster::setup(app.handle().clone());
|
||||
|
||||
// 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| {
|
||||
app.listen_any("single-instance", move |_event| {
|
||||
let _ = window::open_window(&handle);
|
||||
});
|
||||
|
||||
|
@ -196,17 +234,23 @@ fn main() {
|
|||
);
|
||||
|
||||
api.prevent_close();
|
||||
if let Some(window) = handle.get_window(label.as_str()) {
|
||||
let _ = window.emit("exit-requested", "");
|
||||
if let Some(window) = handle.get_webview_window(label.as_str()) {
|
||||
let result = window.emit("exit-requested", "");
|
||||
if let Err(err) = result {
|
||||
error!("failed to emit event: {}", err.to_string());
|
||||
}
|
||||
} else {
|
||||
error!("window was None");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
RunEvent::ExitRequested { api, .. } => {
|
||||
api.prevent_exit();
|
||||
}
|
||||
// TODO(vladimir): why was this needed?
|
||||
// RunEvent::ExitRequested { api, .. } => {
|
||||
// api.prevent_exit();
|
||||
// }
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::PortmasterPlugin;
|
||||
use super::PortmasterInterface;
|
||||
use crate::service::get_service_manager;
|
||||
use crate::service::ServiceManager;
|
||||
use log::debug;
|
||||
|
@ -15,7 +15,7 @@ pub struct Error {
|
|||
#[tauri::command]
|
||||
pub fn should_show<R: Runtime>(
|
||||
_window: Window<R>,
|
||||
portmaster: State<'_, PortmasterPlugin<R>>,
|
||||
portmaster: State<'_, PortmasterInterface<R>>,
|
||||
) -> Result {
|
||||
if portmaster.get_show_after_bootstrap() {
|
||||
debug!("[tauri:rpc:should_show] application should show after bootstrap");
|
||||
|
@ -31,7 +31,7 @@ pub fn should_show<R: Runtime>(
|
|||
#[tauri::command]
|
||||
pub fn should_handle_prompts<R: Runtime>(
|
||||
_window: Window<R>,
|
||||
portmaster: State<'_, PortmasterPlugin<R>>,
|
||||
portmaster: State<'_, PortmasterInterface<R>>,
|
||||
) -> Result {
|
||||
if portmaster.handle_prompts.load(Ordering::Relaxed) {
|
||||
Ok("true".to_string())
|
||||
|
@ -43,7 +43,7 @@ pub fn should_handle_prompts<R: Runtime>(
|
|||
#[tauri::command]
|
||||
pub fn get_state<R: Runtime>(
|
||||
_window: Window<R>,
|
||||
portmaster: State<'_, PortmasterPlugin<R>>,
|
||||
portmaster: State<'_, PortmasterInterface<R>>,
|
||||
key: String,
|
||||
) -> Result {
|
||||
let value = portmaster.get_state(key);
|
||||
|
@ -58,7 +58,7 @@ pub fn get_state<R: Runtime>(
|
|||
#[tauri::command]
|
||||
pub fn set_state<R: Runtime>(
|
||||
_window: Window<R>,
|
||||
portmaster: State<'_, PortmasterPlugin<R>>,
|
||||
portmaster: State<'_, PortmasterInterface<R>>,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> Result {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
/// 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;
|
||||
pub 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
|
||||
|
@ -34,10 +34,9 @@ use std::{
|
|||
use log::{debug, error};
|
||||
use serde;
|
||||
use std::sync::Mutex;
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
AppHandle, Manager, Runtime,
|
||||
};
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
|
||||
const PORTMASTER_BASE_URL: &'static str = "http://127.0.0.1:817/api/v1/";
|
||||
|
||||
pub trait Handler {
|
||||
fn on_connect(&mut self, cli: PortAPI) -> ();
|
||||
|
@ -45,7 +44,7 @@ pub trait Handler {
|
|||
fn name(&self) -> String;
|
||||
}
|
||||
|
||||
pub struct PortmasterPlugin<R: Runtime> {
|
||||
pub struct PortmasterInterface<R: Runtime> {
|
||||
#[allow(dead_code)]
|
||||
app: AppHandle<R>,
|
||||
|
||||
|
@ -76,7 +75,7 @@ pub struct PortmasterPlugin<R: Runtime> {
|
|||
should_show_after_bootstrap: AtomicBool,
|
||||
}
|
||||
|
||||
impl<R: Runtime> PortmasterPlugin<R> {
|
||||
impl<R: Runtime> PortmasterInterface<R> {
|
||||
/// Returns a state stored in the portmaster plugin.
|
||||
pub fn get_state(&self, key: String) -> Option<String> {
|
||||
let map = self.state.lock();
|
||||
|
@ -118,13 +117,12 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
|
||||
handler.on_connect(api);
|
||||
} else {
|
||||
debug!("not yet connected to Portmaster API, calling on_disconnect()");
|
||||
debug!("not yet connected to Portmaster API, calling on_disconnect()");
|
||||
|
||||
handler.on_disconnect();
|
||||
}
|
||||
|
||||
handlers.push(Box::new(handler));
|
||||
|
||||
debug!("number of registered handlers: {}", handlers.len());
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +172,7 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
self.should_show_after_bootstrap.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Tells the angular applicatoin to show the window by emitting an event.
|
||||
/// Tells the angular application 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) {
|
||||
|
@ -184,8 +182,9 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
// 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", "");
|
||||
if let Err(err) = self.app.emit("portmaster:show", "") {
|
||||
error!("failed to emit show event: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables or disables the SPN.
|
||||
|
@ -206,12 +205,30 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Send Shutdown request to portmaster
|
||||
pub fn trigger_shutdown(&self) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let client = reqwest::Client::new();
|
||||
match client
|
||||
.post(format!("{}core/shutdown", PORTMASTER_BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(v) => {
|
||||
debug!("shutdown request sent {:?}", v);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to send shutdown request {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//// 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;
|
||||
notifications::notification_handler(api).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -223,9 +240,10 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
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);
|
||||
{
|
||||
let mut guard = self.api.lock().unwrap();
|
||||
*guard = Some(api.clone());
|
||||
}
|
||||
|
||||
// fire-off the notification handler.
|
||||
if self.handle_notifications.load(Ordering::Relaxed) {
|
||||
|
@ -249,9 +267,10 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
self.is_reachable.store(false, Ordering::Relaxed);
|
||||
|
||||
// clear the current api client reference.
|
||||
let mut guard = self.api.lock().unwrap();
|
||||
*guard = None;
|
||||
drop(guard);
|
||||
{
|
||||
let mut guard = self.api.lock().unwrap();
|
||||
*guard = None;
|
||||
}
|
||||
|
||||
if let Ok(mut handlers) = self.handlers.lock() {
|
||||
for handler in handlers.iter_mut() {
|
||||
|
@ -262,47 +281,32 @@ impl<R: Runtime> PortmasterPlugin<R> {
|
|||
}
|
||||
|
||||
pub trait PortmasterExt<R: Runtime> {
|
||||
fn portmaster(&self) -> &PortmasterPlugin<R>;
|
||||
fn portmaster(&self) -> &PortmasterInterface<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()
|
||||
fn portmaster(&self) -> &PortmasterInterface<R> {
|
||||
self.state::<PortmasterInterface<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),
|
||||
};
|
||||
pub fn setup(app: AppHandle) {
|
||||
let interface = PortmasterInterface {
|
||||
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);
|
||||
app.manage(interface);
|
||||
|
||||
// fire of the websocket handler
|
||||
websocket::start_websocket_thread(app.clone());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
// fire of the websocket handler
|
||||
websocket::start_websocket_thread(app.clone());
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ 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) {
|
||||
|
@ -34,59 +32,7 @@ pub async fn notification_handler(cli: PortAPI) {
|
|||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
show_notification(&cli, key, n).await;
|
||||
}
|
||||
Err(err) => match err {
|
||||
ParseError::JSON(err) => {
|
||||
|
@ -101,3 +47,99 @@ pub async fn notification_handler(cli: PortAPI) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) {
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
let cli_clone = cli.clone();
|
||||
async_runtime::spawn(async move {
|
||||
let res = notif.show();
|
||||
// TODO(ppacher): keep a reference of open notifications and close them
|
||||
// if the user reacted inside the UI:
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) {
|
||||
use tauri_winrt_notification::{Duration, Sound, Toast};
|
||||
|
||||
let mut toast = Toast::new("io.safing.portmaster")
|
||||
.title(&n.title)
|
||||
.text1(&n.message)
|
||||
.sound(Some(Sound::Default))
|
||||
.duration(Duration::Long);
|
||||
|
||||
for action in n.actions {
|
||||
toast = toast.add_button(&action.text, &action.id);
|
||||
}
|
||||
{
|
||||
let cli = cli.clone();
|
||||
toast = toast.on_activated(move |action| -> windows::core::Result<()> {
|
||||
if let Some(value) = action {
|
||||
let cli = cli.clone();
|
||||
let key = key.clone();
|
||||
async_runtime::spawn(async move {
|
||||
let _ = cli
|
||||
.request(Request::Update(
|
||||
key,
|
||||
Payload::JSON(
|
||||
json!({
|
||||
"SelectedActionID": value
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
// TODO(vladimir): If Action is None, the user clicked on the notification. Focus on the UI.
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
toast.show().expect("unable to send notification");
|
||||
// TODO(vladimir): keep a reference of open notifications and close them
|
||||
// if the user reacted inside the UI:
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ use std::process::ExitStatus;
|
|||
#[cfg(target_os = "linux")]
|
||||
use crate::service::systemd::SystemdServiceManager;
|
||||
|
||||
use log::info;
|
||||
use thiserror::Error;
|
||||
|
||||
use self::status::StatusResult;
|
||||
|
@ -63,7 +62,7 @@ pub fn get_service_manager() -> Result<impl ServiceManager> {
|
|||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if SystemdServiceManager::is_installed() {
|
||||
info!("system service manager: systemd");
|
||||
log::info!("system service manager: systemd");
|
||||
|
||||
Ok(SystemdServiceManager {})
|
||||
} else {
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Mutex;
|
||||
use std::{collections::HashMap, sync::atomic::Ordering};
|
||||
|
||||
use log::{debug, error};
|
||||
use tauri::tray::{MouseButton, MouseButtonState};
|
||||
use tauri::{
|
||||
menu::{
|
||||
CheckMenuItem, CheckMenuItemBuilder, MenuBuilder, MenuItemBuilder, PredefinedMenuItem,
|
||||
SubmenuBuilder,
|
||||
},
|
||||
tray::{ClickType, TrayIcon, TrayIconBuilder},
|
||||
Icon, Manager, Wry,
|
||||
image::Image,
|
||||
menu::{MenuBuilder, MenuItem, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder},
|
||||
tray::{TrayIcon, TrayIconBuilder},
|
||||
Wry,
|
||||
};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
use crate::{
|
||||
portapi::{
|
||||
|
@ -26,62 +25,90 @@ use crate::{
|
|||
portmaster::PortmasterExt,
|
||||
window::{create_main_window, may_navigate_to_ui, open_window},
|
||||
};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
pub type AppIcon = TrayIcon<Wry>;
|
||||
|
||||
static SPN_STATE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
lazy_static! {
|
||||
// Set once setup_tray_menu executed.
|
||||
static ref SPN_BUTTON: Mutex<Option<CheckMenuItem<Wry>>> = Mutex::new(None);
|
||||
static ref SPN_STATUS: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
|
||||
static ref SPN_BUTTON: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
|
||||
static ref GLOBAL_STATUS: Mutex<Option<MenuItem<Wry>>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
const PM_TRAY_ICON_ID: &'static str = "pm_icon";
|
||||
|
||||
// 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 BLUE_ICON: &'static [u8] = include_bytes!("../../../../assets/data/icons/pm_light_blue.ico");
|
||||
const RED_ICON: &'static [u8] = include_bytes!("../../../../assets/data/icons/pm_light_red.ico");
|
||||
const YELLOW_ICON: &'static [u8] =
|
||||
include_bytes!("../../assets/icons/pm_light_yellow_512.ico");
|
||||
include_bytes!("../../../../assets/data/icons/pm_light_yellow.ico");
|
||||
const GREEN_ICON: &'static [u8] =
|
||||
include_bytes!("../../assets/icons/pm_light_green_512.ico");
|
||||
include_bytes!("../../../../assets/data/icons/pm_light_green.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 open_btn = MenuItemBuilder::with_id("open", "Open App").build(app)?;
|
||||
let exit_ui_btn = MenuItemBuilder::with_id("exit_ui", "Exit UI").build(app)?;
|
||||
let shutdown_btn = MenuItemBuilder::with_id("shutdown", "Shut Down Portmaster").build(app)?;
|
||||
|
||||
let spn = CheckMenuItemBuilder::with_id("spn", "Use SPN").build(app);
|
||||
let global_status = MenuItemBuilder::with_id("global_status", "Status: Secured")
|
||||
.enabled(false)
|
||||
.build(app)
|
||||
.unwrap();
|
||||
{
|
||||
let mut button_ref = GLOBAL_STATUS.lock()?;
|
||||
*button_ref = Some(global_status.clone());
|
||||
}
|
||||
|
||||
// Store the SPN button reference
|
||||
let mut button_ref = SPN_BUTTON.lock().unwrap();
|
||||
*button_ref = Some(spn.clone());
|
||||
// Setup SPN status
|
||||
let spn_status = MenuItemBuilder::with_id("spn_status", "SPN: Disabled")
|
||||
.enabled(false)
|
||||
.build(app)
|
||||
.unwrap();
|
||||
{
|
||||
let mut button_ref = SPN_STATUS.lock()?;
|
||||
*button_ref = Some(spn_status.clone());
|
||||
}
|
||||
// Setup SPN button
|
||||
let spn = MenuItemBuilder::with_id("spn_toggle", "Enable SPN")
|
||||
.build(app)
|
||||
.unwrap();
|
||||
{
|
||||
let mut button_ref = SPN_BUTTON.lock()?;
|
||||
*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 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,
|
||||
&PredefinedMenuItem::separator(app)?,
|
||||
&global_status,
|
||||
&PredefinedMenuItem::separator(app)?,
|
||||
&spn_status,
|
||||
&spn,
|
||||
&PredefinedMenuItem::separator(app)?,
|
||||
&exit_ui_btn,
|
||||
&shutdown_btn,
|
||||
&developer_menu,
|
||||
])
|
||||
.build()?;
|
||||
|
||||
let icon = TrayIconBuilder::new()
|
||||
.icon(Icon::Raw(RED_ICON.to_vec()))
|
||||
let icon = TrayIconBuilder::with_id(PM_TRAY_ICON_ID)
|
||||
.icon(Image::from_bytes(RED_ICON).unwrap())
|
||||
.menu(&menu)
|
||||
.on_menu_event(move |app, event| match event.id().as_ref() {
|
||||
"close" => {
|
||||
"exit_ui" => {
|
||||
let handle = app.clone();
|
||||
app.dialog()
|
||||
.message("This does not stop the Portmaster system service")
|
||||
|
@ -90,7 +117,7 @@ pub fn setup_tray_menu(
|
|||
.cancel_button_label("No")
|
||||
.show(move |answer| {
|
||||
if answer {
|
||||
let _ = handle.emit("exit-requested", "");
|
||||
// let _ = handle.emit("exit-requested", "");
|
||||
handle.exit(0);
|
||||
}
|
||||
});
|
||||
|
@ -116,27 +143,39 @@ pub fn setup_tray_menu(
|
|||
}
|
||||
};
|
||||
}
|
||||
"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);
|
||||
}
|
||||
"spn_toggle" => {
|
||||
if SPN_STATE.load(Ordering::Acquire) {
|
||||
app.portmaster().set_spn_enabled(false);
|
||||
} else {
|
||||
app.portmaster().set_spn_enabled(true);
|
||||
}
|
||||
}
|
||||
"shutdown" => {
|
||||
app.portmaster().trigger_shutdown();
|
||||
}
|
||||
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());
|
||||
|
||||
if let tauri::tray::TrayIconEvent::Click {
|
||||
id: _,
|
||||
position: _,
|
||||
rect: _,
|
||||
button,
|
||||
button_state,
|
||||
} = event
|
||||
{
|
||||
if let MouseButton::Left = button {
|
||||
if let MouseButtonState::Down = button_state {
|
||||
let _ = open_window(tray.app_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(icon)
|
||||
}
|
||||
|
||||
|
@ -145,19 +184,27 @@ pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_st
|
|||
let failure = subsystems
|
||||
.values()
|
||||
.into_iter()
|
||||
.map(|s| s.failure_status)
|
||||
.fold(
|
||||
subsystem::FAILURE_NONE,
|
||||
|acc, s| {
|
||||
if s > acc {
|
||||
s
|
||||
} else {
|
||||
acc
|
||||
.map(|s| &s.module_status)
|
||||
.fold((subsystem::FAILURE_NONE, "".to_string()), |mut acc, s| {
|
||||
for m in s {
|
||||
if m.failure_status > acc.0 {
|
||||
acc = (m.failure_status, m.failure_msg.clone())
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
acc
|
||||
});
|
||||
|
||||
let next_icon = match failure {
|
||||
if failure.0 == subsystem::FAILURE_NONE {
|
||||
if let Some(global_status) = &mut *(GLOBAL_STATUS.lock().unwrap()) {
|
||||
_ = global_status.set_text("Status: Secured");
|
||||
}
|
||||
} else {
|
||||
if let Some(global_status) = &mut *(GLOBAL_STATUS.lock().unwrap()) {
|
||||
_ = global_status.set_text(format!("Status: {}", failure.1));
|
||||
}
|
||||
}
|
||||
|
||||
let next_icon = match failure.0 {
|
||||
subsystem::FAILURE_WARNING => YELLOW_ICON,
|
||||
subsystem::FAILURE_ERROR => RED_ICON,
|
||||
_ => match spn_status.as_str() {
|
||||
|
@ -166,11 +213,11 @@ pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_st
|
|||
},
|
||||
};
|
||||
|
||||
_ = icon.set_icon(Some(Icon::Raw(next_icon.to_vec())));
|
||||
_ = icon.set_icon(Some(Image::from_bytes(next_icon).unwrap()));
|
||||
}
|
||||
|
||||
pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
||||
let icon = match app.tray() {
|
||||
let icon = match app.tray_by_id(PM_TRAY_ICON_ID) {
|
||||
Some(icon) => icon,
|
||||
None => {
|
||||
error!("cancel try_handler: missing try icon");
|
||||
|
@ -226,7 +273,23 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||
}
|
||||
};
|
||||
|
||||
_ = icon.set_icon(Some(Icon::Raw(BLUE_ICON.to_vec())));
|
||||
let mut portmaster_shutdown_event_subscription = match cli
|
||||
.request(Request::Subscribe(
|
||||
"query runtime:modules/core/event/shutdown".to_string(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(rx) => rx,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"cancel try_handler: failed to subscribe to 'runtime:modules/core/event/shutdown': {}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
_ = icon.set_icon(Some(Image::from_bytes(BLUE_ICON).unwrap()));
|
||||
|
||||
let mut subsystems: HashMap<String, Subsystem> = HashMap::new();
|
||||
let mut spn_status: String = "".to_string();
|
||||
|
@ -312,15 +375,7 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||
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);
|
||||
}
|
||||
}
|
||||
update_spn_ui_state(value.value.unwrap_or(false));
|
||||
},
|
||||
Err(err) => match err {
|
||||
ParseError::JSON(err) => {
|
||||
|
@ -332,13 +387,40 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
msg = portmaster_shutdown_event_subscription.recv() => {
|
||||
let msg = match msg {
|
||||
Some(m) => m,
|
||||
None => { break }
|
||||
};
|
||||
debug!("Shutdown request received: {:?}", msg);
|
||||
match msg {
|
||||
Response::Ok(_, _) | Response::New(_, _) | Response::Update(_, _) => app.exit(0),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(btn) = &mut *(SPN_BUTTON.lock().unwrap()) {
|
||||
_ = btn.set_checked(false);
|
||||
}
|
||||
|
||||
_ = icon.set_icon(Some(Icon::Raw(RED_ICON.to_vec())));
|
||||
update_spn_ui_state(false);
|
||||
_ = icon.set_icon(Some(Image::from_bytes(RED_ICON).unwrap()));
|
||||
}
|
||||
|
||||
fn update_spn_ui_state(enabled: bool) {
|
||||
let mut spn_status = SPN_STATUS.lock().unwrap();
|
||||
let Some(spn_status_ref) = &mut *spn_status else {
|
||||
return;
|
||||
};
|
||||
let mut spn_btn = SPN_BUTTON.lock().unwrap();
|
||||
let Some(spn_btn_ref) = &mut *spn_btn else {
|
||||
return;
|
||||
};
|
||||
if enabled {
|
||||
_ = spn_status_ref.set_text("SPN: Connected");
|
||||
_ = spn_btn_ref.set_text("Disable SPN");
|
||||
} else {
|
||||
_ = spn_status_ref.set_text("SPN: Disabled");
|
||||
_ = spn_btn_ref.set_text("Enable SPN");
|
||||
}
|
||||
SPN_STATE.store(enabled, Ordering::SeqCst);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use log::{debug, error};
|
||||
use tauri::{AppHandle, Manager, Result, UserAttentionType, Window, WindowBuilder, WindowUrl};
|
||||
use tauri::{
|
||||
AppHandle, Manager, Result, UserAttentionType, WebviewUrl, WebviewWindow, WebviewWindowBuilder,
|
||||
};
|
||||
|
||||
use crate::portmaster::PortmasterExt;
|
||||
|
||||
|
@ -11,15 +13,16 @@ use crate::portmaster::PortmasterExt;
|
|||
/// 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") {
|
||||
pub fn create_main_window(app: &AppHandle) -> Result<WebviewWindow> {
|
||||
let mut window = if let Some(window) = app.get_webview_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()))
|
||||
let res = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
|
||||
.title("Portmaster")
|
||||
.visible(false)
|
||||
.build();
|
||||
|
||||
|
@ -54,12 +57,12 @@ pub fn create_main_window(app: &AppHandle) -> Result<Window> {
|
|||
Ok(window)
|
||||
}
|
||||
|
||||
pub fn create_splash_window(app: &AppHandle) -> Result<Window> {
|
||||
if let Some(window) = app.get_window("splash") {
|
||||
pub fn create_splash_window(app: &AppHandle) -> Result<WebviewWindow> {
|
||||
if let Some(window) = app.get_webview_window("splash") {
|
||||
let _ = window.show();
|
||||
Ok(window)
|
||||
} else {
|
||||
let window = WindowBuilder::new(app, "splash", WindowUrl::App("index.html".into()))
|
||||
let window = WebviewWindowBuilder::new(app, "splash", WebviewUrl::App("index.html".into()))
|
||||
.center()
|
||||
.closable(false)
|
||||
.focused(true)
|
||||
|
@ -76,8 +79,9 @@ pub fn create_splash_window(app: &AppHandle) -> Result<Window> {
|
|||
}
|
||||
|
||||
pub fn close_splash_window(app: &AppHandle) -> Result<()> {
|
||||
if let Some(window) = app.get_window("splash") {
|
||||
return window.close();
|
||||
if let Some(window) = app.get_webview_window("splash") {
|
||||
let _ = window.hide();
|
||||
return window.destroy();
|
||||
}
|
||||
return Err(tauri::Error::WindowNotFound);
|
||||
}
|
||||
|
@ -94,9 +98,9 @@ pub fn close_splash_window(app: &AppHandle) -> Result<()> {
|
|||
///
|
||||
/// 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> {
|
||||
pub fn open_window(app: &AppHandle) -> Result<WebviewWindow> {
|
||||
if app.portmaster().is_reachable() {
|
||||
match app.get_window("main") {
|
||||
match app.get_webview_window("main") {
|
||||
Some(win) => {
|
||||
app.portmaster().show_window();
|
||||
|
||||
|
@ -122,32 +126,41 @@ pub fn open_window(app: &AppHandle) -> Result<Window> {
|
|||
/// 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) {
|
||||
pub fn may_navigate_to_ui(win: &mut WebviewWindow, 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().as_str() == "tauri://localhost" {
|
||||
if force || win.label().eq("main") {
|
||||
#[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());
|
||||
_ = win.navigate(target_url.parse().unwrap());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Only for dev build
|
||||
// Allow connection to http://localhost:4200
|
||||
let capabilities = include_str!("../capabilities/default.json")
|
||||
.replace("http://localhost:817", "http://localhost:4200");
|
||||
let _ = win.add_capability(capabilities);
|
||||
debug!("[tauri] navigating to http://localhost:4200");
|
||||
win.navigate("http://localhost:4200".parse().unwrap());
|
||||
_ = win.navigate("http://localhost:4200".parse().unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
win.navigate("http://localhost:817".parse().unwrap());
|
||||
{
|
||||
_ = win.navigate("http://localhost:817".parse().unwrap());
|
||||
}
|
||||
} else {
|
||||
error!("not navigating to user interface: current url: {}", win.url().as_str());
|
||||
error!(
|
||||
"not navigating to user interface: current url: {}",
|
||||
win.url().unwrap().as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
{
|
||||
"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": "Safing Limited Inc",
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libayatana-appindicator3"
|
||||
],
|
||||
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
|
||||
"files": {
|
||||
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
|
||||
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop",
|
||||
"/var/": "../../../packaging/linux/var",
|
||||
"../control/postinst": "../../../packaging/linux/debian/postinst",
|
||||
"../control/postrm": "../../../packaging/linux/debian/postrm"
|
||||
}
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
}
|
106
desktop/tauri/src-tauri/tauri.conf.json5
Normal file
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"build": {
|
||||
"beforeDevCommand": {
|
||||
"script": "npm run tauri-dev",
|
||||
"cwd": "../../angular",
|
||||
"wait": false
|
||||
},
|
||||
"frontendDist": "../../angular/dist/tauri-builtin",
|
||||
"devUrl": "http://localhost:4100"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"productName": "Portmaster",
|
||||
"version": "0.1.0",
|
||||
"identifier": "io.safing.portmaster", // this is added as a property to the shortcut on windows (ApplicationUserModelID). Used for notifications.
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "Utility",
|
||||
"copyright": "Safing Limited Inc",
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libayatana-appindicator3"
|
||||
],
|
||||
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
|
||||
"files": {
|
||||
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
|
||||
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop"
|
||||
},
|
||||
"postInstallScript": "../../../packaging/linux/postinst",
|
||||
"postRemoveScript": "../../../packaging/linux/postrm"
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"libayatana-appindicator-gtk3"
|
||||
],
|
||||
"desktopTemplate": "../../../packaging/linux/portmaster.desktop",
|
||||
"release": "1",
|
||||
"files": {
|
||||
"/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service",
|
||||
"/etc/xdg/autostart/portmaster.desktop": "../../../packaging/linux/portmaster-autostart.desktop"
|
||||
},
|
||||
"postInstallScript": "../../../packaging/linux/postinst",
|
||||
"postRemoveScript": "../../../packaging/linux/postrm"
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"installMode": "perMachine",
|
||||
"installerHooks": "templates/nsis_install_hooks.nsh",
|
||||
"installerIcon": "../../../assets/data/icons/pm_light.ico"
|
||||
},
|
||||
"wix": {
|
||||
"fragmentPaths": [
|
||||
"templates/service.wxs"
|
||||
],
|
||||
"template": "templates/main.wxs"
|
||||
}
|
||||
},
|
||||
"externalBin": [
|
||||
"binaries/portmaster-start",
|
||||
"binaries/portmaster-core"
|
||||
],
|
||||
"targets": [
|
||||
"deb",
|
||||
"rpm",
|
||||
"nsis",
|
||||
"msi"
|
||||
],
|
||||
"icon": [
|
||||
"../../../assets/data/icons/pm_dark_512.png",
|
||||
"../../../assets/data/icons/pm_dark.ico",
|
||||
"../../../assets/data/icons/pm_light_512.png",
|
||||
"../../../assets/data/icons/pm_light.ico"
|
||||
]
|
||||
}
|
||||
}
|
354
desktop/tauri/src-tauri/templates/main.wxs
Normal file
|
@ -0,0 +1,354 @@
|
|||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="arm64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{product_name}}"
|
||||
UpgradeCode="{{upgrade_code}}"
|
||||
Language="!(loc.TauriLanguage)"
|
||||
Manufacturer="{{manufacturer}}"
|
||||
Version="{{version}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="0"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||
|
||||
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
|
||||
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
|
||||
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
|
||||
<Property Id="LAUNCHAPPARGS" Secure="yes" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
{{#if banner_path}}
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||
{{/if}}
|
||||
{{#if dialog_image_path}}
|
||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||
{{/if}}
|
||||
{{#if license}}
|
||||
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||
{{/if}}
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
{{#if homepage}}
|
||||
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
|
||||
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
|
||||
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
|
||||
{{/if}}
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
{{#unless license}}
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg"
|
||||
Control="Next"
|
||||
Event="NewDialog"
|
||||
Value="InstallDirDlg"
|
||||
Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg"
|
||||
Control="Back"
|
||||
Event="NewDialog"
|
||||
Value="WelcomeDlg"
|
||||
Order="2">1</Publish>
|
||||
{{/unless}}
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="DesktopFolder" Name="Desktop">
|
||||
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="RegistryEntries" Guid="*">
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Change the Root to HKCU for perUser installations -->
|
||||
{{#each deep_link_protocols as |protocol| ~}}
|
||||
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
|
||||
<RegistryKey Key="DefaultIcon">
|
||||
<RegistryValue Type="string" Value=""[!Path]",0" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Key="shell\open\command">
|
||||
<RegistryValue Type="string" Value=""[!Path]" "%1"" />
|
||||
</RegistryKey>
|
||||
</RegistryKey>
|
||||
{{/each~}}
|
||||
</Component>
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
{{#each file_associations as |association| ~}}
|
||||
{{#each association.ext as |ext| ~}}
|
||||
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
|
||||
<Extension Id="{{ext}}" Advertise="yes">
|
||||
<Verb Id="open" Command="Open with {{../../product_name}}" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
{{/each~}}
|
||||
{{/each~}}
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{#if enable_elevated_update_task}}
|
||||
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
{{/if}}
|
||||
{{resources}}
|
||||
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{product_name}}"
|
||||
Description="Uninstalls {{product_name}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="INSTALLDIR"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||
Name="Uninstaller Shortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{product_name}}"
|
||||
Description="Runs {{product_name}}"
|
||||
Target="[!Path]"
|
||||
Icon="ProductIcon"
|
||||
WorkingDirectory="INSTALLDIR">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||
</Shortcut>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
{{#each merge_modules as |msm| ~}}
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||
<MergeRef Id="{{ msm.name }}"/>
|
||||
</Feature>
|
||||
{{/each~}}
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="!(loc.InstallAppFeature)"
|
||||
Level="1"
|
||||
ConfigurableDirectory="INSTALLDIR"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
<ComponentRef Id="RegistryEntries"/>
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<ComponentRef Id="UpdateTask" />
|
||||
<ComponentRef Id="UpdateTaskInstaller" />
|
||||
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||
{{/if}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="Path"/>
|
||||
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="!(loc.PathEnvVarFeature)"
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<ComponentRef Id="{{ bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||
{{#each component_group_refs as |id| ~}}
|
||||
<ComponentGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each component_refs as |id| ~}}
|
||||
<ComponentRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_group_refs as |id| ~}}
|
||||
<FeatureGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_refs as |id| ~}}
|
||||
<FeatureRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each merge_refs as |id| ~}}
|
||||
<MergeRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||
<CustomAction
|
||||
Id="CreateUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
Execute="commit"
|
||||
Impersonate="yes"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||
NOT(REMOVE)
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
<!-- Remove elevated update task during uninstall -->
|
||||
<CustomAction
|
||||
Id="DeleteUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<CustomActionRef Id='InstallPortmasterService' />
|
||||
<CustomActionRef Id='StopPortmasterService' />
|
||||
<CustomActionRef Id='DeletePortmasterService' />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
350
desktop/tauri/src-tauri/templates/main_original.wxs
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="arm64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{product_name}}"
|
||||
UpgradeCode="{{upgrade_code}}"
|
||||
Language="!(loc.TauriLanguage)"
|
||||
Manufacturer="{{manufacturer}}"
|
||||
Version="{{version}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="0"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||
|
||||
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
|
||||
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
|
||||
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
|
||||
<Property Id="LAUNCHAPPARGS" Secure="yes" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
{{#if banner_path}}
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||
{{/if}}
|
||||
{{#if dialog_image_path}}
|
||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||
{{/if}}
|
||||
{{#if license}}
|
||||
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||
{{/if}}
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
{{#if homepage}}
|
||||
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
|
||||
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
|
||||
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
|
||||
{{/if}}
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
{{#unless license}}
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg"
|
||||
Control="Next"
|
||||
Event="NewDialog"
|
||||
Value="InstallDirDlg"
|
||||
Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg"
|
||||
Control="Back"
|
||||
Event="NewDialog"
|
||||
Value="WelcomeDlg"
|
||||
Order="2">1</Publish>
|
||||
{{/unless}}
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="DesktopFolder" Name="Desktop">
|
||||
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="RegistryEntries" Guid="*">
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Change the Root to HKCU for perUser installations -->
|
||||
{{#each deep_link_protocols as |protocol| ~}}
|
||||
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
|
||||
<RegistryKey Key="DefaultIcon">
|
||||
<RegistryValue Type="string" Value=""[!Path]",0" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Key="shell\open\command">
|
||||
<RegistryValue Type="string" Value=""[!Path]" "%1"" />
|
||||
</RegistryKey>
|
||||
</RegistryKey>
|
||||
{{/each~}}
|
||||
</Component>
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
{{#each file_associations as |association| ~}}
|
||||
{{#each association.ext as |ext| ~}}
|
||||
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
|
||||
<Extension Id="{{ext}}" Advertise="yes">
|
||||
<Verb Id="open" Command="Open with {{../../product_name}}" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
{{/each~}}
|
||||
{{/each~}}
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{#if enable_elevated_update_task}}
|
||||
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
{{/if}}
|
||||
{{resources}}
|
||||
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{product_name}}"
|
||||
Description="Uninstalls {{product_name}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="INSTALLDIR"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||
Name="Uninstaller Shortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{product_name}}"
|
||||
Description="Runs {{product_name}}"
|
||||
Target="[!Path]"
|
||||
Icon="ProductIcon"
|
||||
WorkingDirectory="INSTALLDIR">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||
</Shortcut>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
{{#each merge_modules as |msm| ~}}
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||
<MergeRef Id="{{ msm.name }}"/>
|
||||
</Feature>
|
||||
{{/each~}}
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="!(loc.InstallAppFeature)"
|
||||
Level="1"
|
||||
ConfigurableDirectory="INSTALLDIR"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
<ComponentRef Id="RegistryEntries"/>
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<ComponentRef Id="UpdateTask" />
|
||||
<ComponentRef Id="UpdateTaskInstaller" />
|
||||
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||
{{/if}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="Path"/>
|
||||
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="!(loc.PathEnvVarFeature)"
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<ComponentRef Id="{{ bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||
{{#each component_group_refs as |id| ~}}
|
||||
<ComponentGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each component_refs as |id| ~}}
|
||||
<ComponentRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_group_refs as |id| ~}}
|
||||
<FeatureGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_refs as |id| ~}}
|
||||
<FeatureRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each merge_refs as |id| ~}}
|
||||
<MergeRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||
<CustomAction
|
||||
Id="CreateUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
Execute="commit"
|
||||
Impersonate="yes"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||
NOT(REMOVE)
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
<!-- Remove elevated update task during uninstall -->
|
||||
<CustomAction
|
||||
Id="DeleteUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
14
desktop/tauri/src-tauri/templates/nsis_install_hooks.nsh
Normal file
|
@ -0,0 +1,14 @@
|
|||
!define NSIS_HOOK_POSTINSTALL "NSIS_HOOK_POSTINSTALL_"
|
||||
|
||||
!macro NSIS_HOOK_POSTINSTALL_
|
||||
ExecWait '"$INSTDIR\portmaster-start.exe" install core-service --data="$INSTDIR\data"'
|
||||
!macroend
|
||||
|
||||
|
||||
!define NSIS_HOOK_PREUNINSTALL "NSIS_HOOK_PREUNINSTALL_"
|
||||
|
||||
!macro NSIS_HOOK_PREUNINSTALL_
|
||||
ExecWait 'sc.exe stop PortmasterCore'
|
||||
ExecWait 'sc.exe delete PortmasterCore'
|
||||
!macroend
|
||||
|
37
desktop/tauri/src-tauri/templates/service.wxs
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<CustomAction Id="InstallPortmasterService"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand=""[INSTALLDIR]portmaster-start.exe" install core-service --data="[INSTALLDIR]data""
|
||||
Execute="commit"
|
||||
Return="check"
|
||||
Impersonate="no"
|
||||
/>
|
||||
<CustomAction Id="StopPortmasterService"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="sc.exe stop PortmasterCore"
|
||||
Execute="commit"
|
||||
Return="ignore"
|
||||
Impersonate="no"
|
||||
/>
|
||||
<CustomAction Id="DeletePortmasterService"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="sc.exe delete PortmasterCore"
|
||||
Execute="commit"
|
||||
Return="ignore"
|
||||
Impersonate="no"
|
||||
/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="InstallPortmasterService" Before='InstallFinalize'>
|
||||
<![CDATA[NOT Installed]]>
|
||||
</Custom>
|
||||
<Custom Action="StopPortmasterService" Before='InstallFinalize'>
|
||||
REMOVE
|
||||
</Custom>
|
||||
<Custom Action="DeletePortmasterService" Before='InstallFinalize'>
|
||||
REMOVE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
</Fragment>
|
||||
</Wix>
|
|
@ -33,10 +33,9 @@ PrivateDevices=yes
|
|||
AmbientCapabilities=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid
|
||||
CapabilityBoundingSet=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid
|
||||
StateDirectory=portmaster
|
||||
ExecStartPre=-/usr/bin/portmaster-start --data $STATE_DIRECTORY clean-structure
|
||||
# TODO(ppacher): add --disable-software-updates once it's merged and the release process changed.
|
||||
ExecStart=/usr/bin/portmaster-core --data $STATE_DIRECTORY $PORTMASTER_ARGS
|
||||
ExecStartPost=-/usr/bin/portmaster-start recover-iptables
|
||||
ExecStart=/usr/bin/portmaster-start --data /opt/safing/portmaster core -- $PORTMASTER_ARGS
|
||||
ExecStopPost=-/usr/bin/portmaster-start recover-iptables
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -57,6 +57,7 @@ func MandatoryUpdates() (identifiers []string) {
|
|||
PlatformIdentifier("start/portmaster-start.exe"),
|
||||
PlatformIdentifier("notifier/portmaster-notifier.exe"),
|
||||
PlatformIdentifier("notifier/portmaster-wintoast.dll"),
|
||||
PlatformIdentifier("app2/portmaster-app.zip"),
|
||||
)
|
||||
} else {
|
||||
identifiers = append(
|
||||
|
@ -64,6 +65,7 @@ func MandatoryUpdates() (identifiers []string) {
|
|||
PlatformIdentifier("core/portmaster-core"),
|
||||
PlatformIdentifier("start/portmaster-start"),
|
||||
PlatformIdentifier("notifier/portmaster-notifier"),
|
||||
PlatformIdentifier("app2/portmaster-app"),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -88,5 +90,6 @@ func AutoUnpackUpdates() []string {
|
|||
|
||||
return []string{
|
||||
PlatformIdentifier("app/portmaster-app.zip"),
|
||||
PlatformIdentifier("app2/portmaster-app.zip"),
|
||||
}
|
||||
}
|
||||
|
|