Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,30 @@ jobs:

ls -la .

- name: Build macOS arm64 desktop image & extract app bundle
run: |
docker build . --target desktop-macos --build-arg RID=osx-arm64 --build-arg VERSION=${{ env.VERSION }} -t tmp-desktop-macos-arm64
docker create --name tmp-desktop-macos-arm64 tmp-desktop-macos-arm64

docker cp tmp-desktop-macos-arm64:/app/pkvault-${{ env.VERSION }}-osx-arm64.app.zip pkvault-${{ env.VERSION }}-osx-arm64.app.zip

docker rm tmp-desktop-macos-arm64
docker rmi tmp-desktop-macos-arm64

ls -la .

- name: Build macOS x64 desktop image & extract app bundle
run: |
docker build . --target desktop-macos --build-arg RID=osx-x64 --build-arg VERSION=${{ env.VERSION }} -t tmp-desktop-macos-x64
docker create --name tmp-desktop-macos-x64 tmp-desktop-macos-x64

docker cp tmp-desktop-macos-x64:/app/pkvault-${{ env.VERSION }}-osx-x64.app.zip pkvault-${{ env.VERSION }}-osx-x64.app.zip

docker rm tmp-desktop-macos-x64
docker rmi tmp-desktop-macos-x64

ls -la .

- name: Create tag
if: ${{ !inputs.dry-run }}
env:
Expand All @@ -249,3 +273,5 @@ jobs:
pkvault-${{ env.VERSION }}.AppImage
pkvault-${{ env.VERSION }}.flatpak
pkvault-${{ env.VERSION }}.deb
pkvault-${{ env.VERSION }}-osx-arm64.app.zip
pkvault-${{ env.VERSION }}-osx-x64.app.zip
46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,46 @@ RUN if [ "$(echo $RID | grep -o 'win-x64')" ]; then \
echo "=== Skip AppImage (non-linux-x64: $RID) ==="; \
fi

# desktop macOS - .app bundle (dotnet cross-compiles, no real Mac required)
FROM desktop-publish AS desktop-publish-macos-app

ARG VERSION
ENV VERSION=${VERSION}

COPY ./PKVault.Desktop/publishers/common ./PKVault.Desktop/publishers/common
COPY ./PKVault.Desktop/publishers/macos ./PKVault.Desktop/publishers/macos

RUN apt-get update && \
apt-get install -y --no-install-recommends \
python3 python3-pip zip curl && \
pip3 install --break-system-packages icnsutil && \
apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/*

# rcodesign: cross-platform codesign, required for ad-hoc signing on Apple Silicon
# https://github.com/indygreg/apple-platform-rs
ARG APPLE_CODESIGN_VERSION=0.29.0
RUN ARCH=$(dpkg --print-architecture) && \
case "$ARCH" in \
amd64) RCODESIGN_TARGET=x86_64-unknown-linux-musl ;; \
arm64) RCODESIGN_TARGET=aarch64-unknown-linux-musl ;; \
*) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
esac && \
curl -sL -o /tmp/apple-codesign.tar.gz \
https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign/${APPLE_CODESIGN_VERSION}/apple-codesign-${APPLE_CODESIGN_VERSION}-${RCODESIGN_TARGET}.tar.gz && \
tar -xzf /tmp/apple-codesign.tar.gz -C /tmp && \
mv /tmp/apple-codesign-${APPLE_CODESIGN_VERSION}-${RCODESIGN_TARGET}/rcodesign /usr/local/bin/rcodesign && \
rm -rf /tmp/apple-codesign.tar.gz /tmp/apple-codesign-${APPLE_CODESIGN_VERSION}-${RCODESIGN_TARGET} && \
rcodesign --version

RUN mkdir -p /app/publish-final

WORKDIR /src/PKVault.Desktop/publishers/macos

RUN chmod +x build-app.sh && \
./build-app.sh ${RID} ${VERSION} /app/publish /app/publish-final-app && \
cd /app/publish-final-app && \
zip -r -y /app/publish-final/pkvault-${VERSION}-${RID}.app.zip PKVault.app

FROM desktop-publish AS desktop-publish-linux-base

COPY ./PKVault.Desktop/publishers/common ./PKVault.Desktop/publishers/common
Expand Down Expand Up @@ -174,6 +214,12 @@ COPY --from=desktop-publish /app/publish-final /app

RUN ls -la /app

FROM alpine:latest AS desktop-macos

COPY --from=desktop-publish-macos-app /app/publish-final /app

RUN ls -la /app

FROM alpine:latest AS desktop-linux

COPY --from=desktop-publish /app/publish/PKVault /tmp/raw/PKVault
Expand Down
5 changes: 5 additions & 0 deletions PKVault.Backend.Tests/utils/PathUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public static string GetExpectedAppDirectory()
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) ?? "~/", "pkvault"));
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) ?? "~/", "pkvault"));
}

var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
return Path.GetFullPath(Path.GetDirectoryName(exePath)!);
}
Expand Down
1 change: 1 addition & 0 deletions PKVault.Backend/settings/dto/SettingsDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum RuntimeSystem
WINDOWS,
LINUX,
STEAMDECK,
MACOS,
}

public enum SourceProvider
Expand Down
14 changes: 14 additions & 0 deletions PKVault.Backend/settings/services/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,17 @@ public static string GetAppDirectory()
);
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return Path.GetFullPath(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
?? "~/",
"pkvault"
)
);
}

var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
var exeDirectory = exePath != null ? Path.GetDirectoryName(exePath) : null;

Expand Down Expand Up @@ -272,6 +283,9 @@ private static RuntimeSystem GetRuntimeSystem()
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return RuntimeSystem.WINDOWS;

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return RuntimeSystem.MACOS;

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return RuntimeSystem.UNKNOWN;

Expand Down
6 changes: 4 additions & 2 deletions PKVault.Backend/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4170,14 +4170,16 @@
"DOCKER",
"WINDOWS",
"LINUX",
"STEAMDECK"
"STEAMDECK",
"MACOS"
],
"enum": [
0,
1,
2,
3,
4
4,
5
]
},
"SourceProvider": {
Expand Down
22 changes: 19 additions & 3 deletions PKVault.Desktop/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Program
{
private static readonly bool WindowsOS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly bool LinuxOS = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
// private static readonly bool MacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static readonly bool MacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
private static readonly string AssemblyStaticPrefix = "PKVault.Desktop.Resources.wwwroot.";
Expand Down Expand Up @@ -312,8 +312,9 @@ async Task<FileExploreResponseMessage> GetDialogResponse()
{
var openFolderRequest = JsonSerializer.Deserialize(message, messageJsonContext.OpenFolderRequestMessage);

var path = MatcherUtil.NormalizePath(Path.Combine(SettingsService.GetAppDirectory(), openFolderRequest.path))
.Replace('/', '\\');
var normalizedPath = MatcherUtil.NormalizePath(Path.Combine(SettingsService.GetAppDirectory(), openFolderRequest.path));

var path = normalizedPath.Replace('/', '\\');

if (WindowsOS)
{
Expand Down Expand Up @@ -365,6 +366,21 @@ async Task<FileExploreResponseMessage> GetDialogResponse()
Process.Start(fallback);
}
}
else if (MacOS)
{
// `open -R` reveals and selects a file/folder in Finder
var arg = $"-R \"{MatcherUtil.NormalizePath(normalizedPath)}\"";

var psi = new ProcessStartInfo
{
FileName = "open",
Arguments = arg,
UseShellExecute = false
};

Log.Logger.Debug($"RUN open {arg}");
Process.Start(psi);
}
else
{
throw new PlatformNotSupportedException($"OS not supported: {RuntimeInformation.OSDescription}");
Expand Down
30 changes: 30 additions & 0 deletions PKVault.Desktop/publishers/macos/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>PKVault</string>
<key>CFBundleDisplayName</key>
<string>PKVault</string>
<key>CFBundleIdentifier</key>
<string>com.chnapy.pkvault</string>
<key>CFBundleVersion</key>
<string>(VERSION)</string>
<key>CFBundleShortVersionString</key>
<string>(VERSION)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleExecutable</key>
<string>pkvault</string>
<key>CFBundleIconFile</key>
<string>pkvault.icns</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHumanReadableCopyright</key>
<string>Richard Haddad</string>
</dict>
</plist>
72 changes: 72 additions & 0 deletions PKVault.Desktop/publishers/macos/build-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/sh

set -e

# Build PKVault.app in macOS context (also runs on Linux, no real Mac required)
# Usage: ./build-app.sh <rid> <version> <publish-dir> <output-dir>

# Note: falls back to "icnsutil" (pip) and "rcodesign" when iconutil/codesign
# aren't available (i.e. running on Linux).

RID="${1:-osx-arm64}"
VERSION="${2:-dev}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PUBLISH_DIR="${3:-$SCRIPT_DIR/../../../out/$RID}"
OUT_DIR="${4:-$SCRIPT_DIR/../../../out}"
APP_DIR="$OUT_DIR/PKVault.app"

echo "=== Building PKVault.app for $RID ==="

rm -rf "$APP_DIR"
mkdir -p "$APP_DIR/Contents/MacOS"
mkdir -p "$APP_DIR/Contents/Resources"

# copy exe (excluding app-generated data and loose files that break signing)
find "$PUBLISH_DIR" -mindepth 1 -maxdepth 1 \
! -name logs ! -name db ! -name backup ! -name '*.sav' \
! -name '*.pdb' ! -name '*.runtimeconfig.json' ! -name swagger.json \
-exec cp -r {} "$APP_DIR/Contents/MacOS/" \;
mv "$APP_DIR/Contents/MacOS/PKVault" "$APP_DIR/Contents/MacOS/pkvault"
chmod 755 "$APP_DIR/Contents/MacOS/pkvault"

# Info.plist
sed -e "s/(VERSION)/$VERSION/g" "$SCRIPT_DIR/Info.plist" > "$APP_DIR/Contents/Info.plist"

# icon: build .icns from the common iconset
ICONSET_DIR=$(mktemp -d)/pkvault.iconset
mkdir -p "$ICONSET_DIR"
COMMON_ICONS="$SCRIPT_DIR/../common/icons"

cp "$COMMON_ICONS/pkvault_16x16.png" "$ICONSET_DIR/icon_16x16.png"
cp "$COMMON_ICONS/pkvault_32x32.png" "$ICONSET_DIR/icon_16x16@2x.png"
cp "$COMMON_ICONS/pkvault_32x32.png" "$ICONSET_DIR/icon_32x32.png"
cp "$COMMON_ICONS/pkvault_64x64.png" "$ICONSET_DIR/icon_32x32@2x.png"
cp "$COMMON_ICONS/pkvault_128x128.png" "$ICONSET_DIR/icon_128x128.png"
cp "$COMMON_ICONS/pkvault_256x256.png" "$ICONSET_DIR/icon_128x128@2x.png"
cp "$COMMON_ICONS/pkvault_256x256.png" "$ICONSET_DIR/icon_256x256.png"
# note: no icon_256x256@2x.png, would need a 512px source asset we don't have

if command -v iconutil >/dev/null 2>&1; then
iconutil -c icns "$ICONSET_DIR" -o "$APP_DIR/Contents/Resources/pkvault.icns"
else
python3 -m icnsutil compose -f "$APP_DIR/Contents/Resources/pkvault.icns" \
"$ICONSET_DIR"/icon_16x16.png \
"$ICONSET_DIR"/icon_16x16@2x.png \
"$ICONSET_DIR"/icon_32x32.png \
"$ICONSET_DIR"/icon_32x32@2x.png \
"$ICONSET_DIR"/icon_128x128.png \
"$ICONSET_DIR"/icon_128x128@2x.png \
"$ICONSET_DIR"/icon_256x256.png
fi
rm -rf "$(dirname "$ICONSET_DIR")"

# ad-hoc sign (required on Apple Silicon)
if command -v codesign >/dev/null 2>&1; then
codesign --force --deep --sign - "$APP_DIR"
elif command -v rcodesign >/dev/null 2>&1; then
rcodesign sign "$APP_DIR"
else
echo "WARNING: neither codesign nor rcodesign found, PKVault.app will be unsigned" >&2
fi

echo "=== PKVault.app created at $APP_DIR ==="
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
PKVault is a Pokémon storage & save manipulation tool based on [PKHeX](https://github.com/kwsch/PKHeX).
Similar to Pokémon Home, offline as online.

<img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg/microsoft-windows.svg" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg/linux.svg" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/steam-deck-light.png" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/docker.png" width="100" /> |
|:---:|:---:|:---:|:---:|
| [![Download for Windows](https://img.shields.io/badge/Windows-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | <a href='https://flathub.org/apps/details/io.github.chnapy.pkvault'><img width='110' alt='Download on Flathub' src='https://flathub.org/api/badge?locale=en' /></a><br/>[![Download for Linux](https://img.shields.io/badge/Linux-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | <a href='https://flathub.org/apps/details/io.github.chnapy.pkvault'><img width='110' alt='Download on Flathub' src='https://flathub.org/api/badge?locale=en' /></a><br/>[![Download for SteamDeck](https://img.shields.io/badge/SteamDeck-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | [![Docker usage](https://img.shields.io/badge/Docker-Setup-purple)](#docker-usage) |
| PKVault.exe | pkvault.flatpak<br/>pkvault.AppImage<br/>pkvault.deb<br/>pkvault.linux | pkvault.flatpak | `ghcr.io/chnapy/pkvault` |
<img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg/microsoft-windows.svg" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg/linux.svg" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg/apple-light.svg" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/steam-deck-light.png" width="100" /> | <img src="https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/docker.png" width="100" /> |
|:---:|:---:|:---:|:---:|:---:|
| [![Download for Windows](https://img.shields.io/badge/Windows-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | <a href='https://flathub.org/apps/details/io.github.chnapy.pkvault'><img width='110' alt='Download on Flathub' src='https://flathub.org/api/badge?locale=en' /></a><br/>[![Download for Linux](https://img.shields.io/badge/Linux-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | [![Download for macOS](https://img.shields.io/badge/macOS-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | <a href='https://flathub.org/apps/details/io.github.chnapy.pkvault'><img width='110' alt='Download on Flathub' src='https://flathub.org/api/badge?locale=en' /></a><br/>[![Download for SteamDeck](https://img.shields.io/badge/SteamDeck-Release-blue)](https://github.com/Chnapy/PKVault/releases/latest) | [![Docker usage](https://img.shields.io/badge/Docker-Setup-purple)](#docker-usage) |
| PKVault.exe | pkvault.flatpak<br/>pkvault.AppImage<br/>pkvault.deb<br/>pkvault.linux | pkvault-osx-arm64.app.zip<br/>pkvault-osx-x64.app.zip | pkvault.flatpak | `ghcr.io/chnapy/pkvault` |

![License](https://img.shields.io/badge/License-GPLv3-green.svg)

Expand Down Expand Up @@ -67,6 +67,8 @@ On Windows, just use `PKVault.exe`.
On Linux & SteamDeck, it's recommanded to get PKVault from Flathub: <a href='https://flathub.org/apps/details/io.github.chnapy.pkvault'><img width='110' alt='Download on Flathub' src='https://flathub.org/api/badge?locale=en' /></a><br/>
Otherwise there is plenty of Linux executables in [releases](https://github.com/Chnapy/PKVault/releases/latest) page.

On macOS, download `pkvault-osx-arm64.app.zip` (Apple Silicon) or `pkvault-osx-x64.app.zip` (Intel), unzip it, then open `PKVault.app`. Since the app isn't notarized, macOS will show "Apple could not verify 'PKVault' is free of malware that may harm your Mac or compromise your privacy." Go to **System Settings > Privacy & Security**, scroll down to find PKVault, and click "Open Anyway", then confirm in the dialog that appears.

### Steam-based usage

<img src="img/steam_1.png" alt="Steam cover" align="right" />
Expand Down
4 changes: 3 additions & 1 deletion docs/functional/en/0-technical.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0 - Technical considerations

PKVault runs on Windows & Linux for its desktop version. A Steamdeck version exists (flatpak).
PKVault runs on Windows, Linux & macOS for its desktop version. A Steamdeck version exists (flatpak).

Like PKHeX, PKVault depends on .NET 10 to run. Since the application is multi-platform and based on web technologies, PKVault also depends on web controls depending on current OS:

Expand All @@ -20,6 +20,8 @@ On Linux, folder used is one of:
- `/home/$USER/Documents/pkvault`
- `/home/$USER/.var/app/io.github.chnapy.pkvault/data` - expected with flatpak file

On macOS, folder used is `/Users/$USER/Documents/pkvault`.

You will find the following files:

- `config/pkvault.json` - The PKVault configuration file
Expand Down
4 changes: 3 additions & 1 deletion docs/functional/pt-br/0-technical.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0 - Considerações técnicas

O PKVault roda no Windows e no Linux em sua versão para desktop. Existe também uma versão para Steam Deck (flatpak).
O PKVault roda no Windows, Linux e macOS em sua versão para desktop. Existe também uma versão para Steam Deck (flatpak).

Assim como o PKHeX, o PKVault depende do .NET 10 para funcionar. Como o aplicativo é multiplataforma e baseado em tecnologias web, o PKVault também depende de componentes de visualização web específicos para o sistema operacional em uso:

Expand All @@ -20,6 +20,8 @@ No Linux, a pasta utilizada é uma das seguintes:
- `/home/$USER/Documents/pkvault`
- `/home/$USER/.var/app/io.github.chnapy.pkvault/data` - esperado ao usar a versão Flatpak

No macOS, a pasta utilizada é `/Users/$USER/Documents/pkvault`.

Você encontrará os seguintes arquivos:

- `config/pkvault.json` - O arquivo de configuração do PKVault
Expand Down
1 change: 1 addition & 0 deletions frontend/src/settings/main/settings-main-left.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const SettingsMainLeft: React.FC = () => {
[ RuntimeSystem.WINDOWS ]: t('settings.system.windows'),
[ RuntimeSystem.LINUX ]: t('settings.system.linux'),
[ RuntimeSystem.STEAMDECK ]: t('settings.system.steamdeck'),
[ RuntimeSystem.MACOS ]: t('settings.system.macos'),
})}</div>
<UIInputLabel leftSection={<img src="https://projectpokemon.org/favicon.ico" />} label='PKHeX' />
<div>{settings?.pkhexVersion}</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translate/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
"settings.sub.external": "Externe pkms",
"settings.sub.main": "Hauptsächlich",
"settings.system.linux": "Linux",
"settings.system.macos": "macOS",
"settings.system.steamdeck": "SteamDeck",
"settings.system.unknown": "System unbekannt",
"settings.system.windows": "Windows",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/translate/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
"settings.sub.external": "External pkms",
"settings.sub.main": "Main",
"settings.system.linux": "Linux desktop",
"settings.system.macos": "macOS desktop",
"settings.system.steamdeck": "SteamDeck",
"settings.system.unknown": "System unknown",
"settings.system.windows": "Windows desktop",
Expand Down
Loading