From 59b32ba10a676158bde81022deba9a2fa061800f Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 00:17:33 +0100 Subject: [PATCH 01/36] Add a dlangui frontend (non functional) --- .../workflows/{build.yml => build-cli.yml} | 66 ++-- .github/workflows/build-dlangui.yml | 314 +++++++++++++++ .github/workflows/build-gtk.yml | 134 +++++++ dlangui/frontend.d | 86 ++++ dlangui/ui/mainframe.d | 373 ++++++++++++++++++ dlangui/ui/utils.d | 56 +++ dub.json | 34 +- dub.selections.json | 4 + linux/gtk/ui/devicewidget.d | 3 + linux/gtk/ui/toolselectionwindow.d | 5 +- source/constants.d | 8 + source/imobiledevice/package.d | 5 +- source/sideload/application.d | 6 +- source/tools/package.d | 9 +- windows/common/graphical_app.d | 19 + windows/{winforms => common}/logging.d | 2 +- windows/winforms/frontend.d | 16 +- windows/winforms/ui/sideloaderform.d | 9 +- 18 files changed, 1076 insertions(+), 73 deletions(-) rename .github/workflows/{build.yml => build-cli.yml} (82%) create mode 100644 .github/workflows/build-dlangui.yml create mode 100644 .github/workflows/build-gtk.yml create mode 100644 dlangui/frontend.d create mode 100644 dlangui/ui/mainframe.d create mode 100644 dlangui/ui/utils.d create mode 100644 windows/common/graphical_app.d rename windows/{winforms => common}/logging.d (94%) diff --git a/.github/workflows/build.yml b/.github/workflows/build-cli.yml similarity index 82% rename from .github/workflows/build.yml rename to .github/workflows/build-cli.yml index 29cd942..3ba0e10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-cli.yml @@ -1,4 +1,4 @@ -name: Sideloader builds +name: CLI builds on: push @@ -25,20 +25,20 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk-static + run: dub build -b release-debug --compiler=ldc2 -c cli - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-x86_64" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-x86_64" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-x86_64.dbg" "${{github.workspace}}/bin/sideloader-x86_64" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-cli-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-cli-linux-x86_64" - uses: actions/upload-artifact@v3 with: - name: sideloader-x86_64 + name: sideloader-cli-linux-x86_64 path: | - ${{github.workspace}}/bin/sideloader-x86_64 - ${{github.workspace}}/bin/sideloader-x86_64.dbg + ${{github.workspace}}/bin/sideloader-cli-linux-x86_64 + ${{github.workspace}}/bin/sideloader-cli-linux-x86_64.dbg build-i686: # Does not work yet @@ -63,20 +63,20 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk-static --arch i686-linux-gnu + run: dub build -b release-debug --compiler=ldc2 -c cli --arch i686-linux-gnu - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-i686" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-i686" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-i686.dbg" "${{github.workspace}}/bin/sideloader-i686" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-cli-linux-i686.dbg" "${{github.workspace}}/bin/sideloader-cli-linux-i686" - uses: actions/upload-artifact@v3 with: - name: sideloader-i686 + name: sideloader-cli-linux-i686 path: | - ${{github.workspace}}/bin/sideloader-i686 - ${{github.workspace}}/bin/sideloader-i686.dbg + ${{github.workspace}}/bin/sideloader-cli-linux-i686 + ${{github.workspace}}/bin/sideloader-cli-linux-i686.dbg build-aarch64: runs-on: ubuntu-22.04 @@ -118,20 +118,20 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk-static --arch aarch64-linux-gnu + run: dub build -b release-debug --compiler=ldc2 -c cli --arch aarch64-linux-gnu - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-aarch64" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-aarch64" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-aarch64.dbg" "${{github.workspace}}/bin/sideloader-aarch64" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-cli-linux-aarch64.dbg" "${{github.workspace}}/bin/sideloader-cli-linux-aarch64" - uses: actions/upload-artifact@v3 with: - name: sideloader-aarch64 + name: sideloader-cli-linux-aarch64 path: | - ${{github.workspace}}/bin/sideloader-aarch64 - ${{github.workspace}}/bin/sideloader-aarch64.dbg + ${{github.workspace}}/bin/sideloader-cli-linux-aarch64 + ${{github.workspace}}/bin/sideloader-cli-linux-aarch64.dbg build-macos-x86_64: runs-on: ubuntu-22.04 @@ -184,16 +184,16 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug -c cli --compiler=ldc2 --arch x86_64-apple-darwin + run: dub build -b release-debug --compiler=ldc2 -c cli --arch x86_64-apple-darwin - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-macOS-x86_64" # TODO make an app bundle + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-macOS-x86_64" # TODO make an app bundle - uses: actions/upload-artifact@v3 with: - name: sideloader-macOS-x86_64 + name: sideloader-cli-macOS-x86_64 path: | - ${{github.workspace}}/bin/sideloader-macOS-x86_64 + ${{github.workspace}}/bin/sideloader-cli-macOS-x86_64 build-macos-arm64: runs-on: ubuntu-22.04 @@ -246,16 +246,16 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug -c cli --compiler=ldc2 --arch arm64-apple-macos + run: dub build -b release-debug --compiler=ldc2 -c cli --arch arm64-apple-macos - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-macOS-arm64" # TODO make an app bundle + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-macOS-arm64" # TODO make an app bundle - uses: actions/upload-artifact@v3 with: - name: sideloader-macOS-arm64 + name: sideloader-cli-macOS-arm64 path: | - ${{github.workspace}}/bin/sideloader-macOS-arm64 + ${{github.workspace}}/bin/sideloader-cli-macOS-arm64 build-windows-x86_64: runs-on: ubuntu-22.04 @@ -299,16 +299,16 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 --arch x86_64-windows-msvc + run: dub build -b release-debug --compiler=ldc2 -c cli --arch x86_64-windows-msvc - name: Rename run: | - mv "${{github.workspace}}/bin/sideloader.exe" "${{github.workspace}}/bin/sideloader-windows-x86_64.exe" - mv "${{github.workspace}}/bin/sideloader.pdb" "${{github.workspace}}/bin/sideloader-windows-x86_64.pdb" + mv "${{github.workspace}}/bin/sideloader.exe" "${{github.workspace}}/bin/sideloader-cli-windows-x86_64.exe" + mv "${{github.workspace}}/bin/sideloader.pdb" "${{github.workspace}}/bin/sideloader-cli-windows-x86_64.pdb" - uses: actions/upload-artifact@v3 with: - name: sideloader-windows-x86_64 + name: sideloader-cli-windows-x86_64 path: | - ${{github.workspace}}/bin/sideloader-windows-x86_64.exe - ${{github.workspace}}/bin/sideloader-windows-x86_64.pdb + ${{github.workspace}}/bin/sideloader-cli-windows-x86_64.exe + ${{github.workspace}}/bin/sideloader-cli-windows-x86_64.pdb diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml new file mode 100644 index 0000000..7a92861 --- /dev/null +++ b/.github/workflows/build-dlangui.yml @@ -0,0 +1,314 @@ +name: DlangUI builds + +on: push + +env: + BUILD_TYPE: Release + +jobs: + build-x86_64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-linux-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64 + ${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64.dbg + + build-i686: + # Does not work yet + if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch i686-linux-gnu + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-i686" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-dlangui-linux-i686.dbg" "${{github.workspace}}/bin/sideloader-dlangui-linux-i686" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-linux-i686 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-linux-i686 + ${{github.workspace}}/bin/sideloader-dlangui-linux-i686.dbg + + build-aarch64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up aarch64 cross-compilation + run: | + mkdir -p $HOME/.ldc/ + cat << EOF | tee $HOME/.ldc/ldc2.conf + "aarch64-.*-linux-gnu": + { + switches = [ + "-defaultlib=phobos2-ldc,druntime-ldc", + "-gcc=aarch64-linux-gnu-gcc", + ]; + post-switches = [ + "-I$HOME/ldc2-1.32.2-linux-aarch64/import", + ]; + lib-dirs = [ + "$HOME/ldc2-1.32.2-linux-aarch64/lib", + ]; + }; + EOF + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.32.2/ldc2-1.32.2-linux-aarch64.tar.xz + tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch aarch64-linux-gnu + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64.dbg" "${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-linux-aarch64 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64 + ${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64.dbg + + build-macos-x86_64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "x86_64-apple-darwin": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld", + "-Xcc=-target", + "-Xcc=x86_64-apple-darwin", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-x86_64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-x86_64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-x86_64.tar.xz + tar -xf ./ldc2-1.33.0-osx-x86_64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch x86_64-apple-darwin + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-macOS-x86_64" # TODO make an app bundle + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-macOS-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-macOS-x86_64 + + build-macos-arm64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "arm64-apple-macos": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld", + "-Xcc=-target", + "-Xcc=arm64-apple-macos", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-arm64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-arm64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-arm64.tar.xz + tar -xf ./ldc2-1.33.0-osx-arm64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch arm64-apple-macos + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-macOS-arm64" # TODO make an app bundle + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-macOS-arm64 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-macOS-arm64 + + build-windows-x86_64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld 7zip + + - name: Set-up Windows cross-compilation + run: | + mkdir -p $HOME/.ldc/ + cat << EOF | tee $HOME/.ldc/ldc2.conf + "x86_64-.*-windows-msvc": + { + // default switches injected before all explicit command-line switches + switches = [ + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-windows-x64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-windows-x64/lib", + ]; + }; + EOF + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-windows-x64.7z + 7z x ./ldc2-1.33.0-windows-x64.7z -o$HOME + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch x86_64-windows-msvc + + - name: Rename + run: | + mv "${{github.workspace}}/bin/sideloader.exe" "${{github.workspace}}/bin/sideloader-dlangui-windows-x86_64.exe" + mv "${{github.workspace}}/bin/sideloader.pdb" "${{github.workspace}}/bin/sideloader-dlangui-windows-x86_64.pdb" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-dlangui-windows-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-dlangui-windows-x86_64.exe + ${{github.workspace}}/bin/sideloader-dlangui-windows-x86_64.pdb diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml new file mode 100644 index 0000000..0fab8cd --- /dev/null +++ b/.github/workflows/build-gtk.yml @@ -0,0 +1,134 @@ +name: GTK builds + +on: push + +env: + BUILD_TYPE: Release + +jobs: + build-x86_64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c linux-gtk + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-linux-gtk-linux-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64 + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64.dbg + + build-i686: + # Does not work yet + if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c linux-gtk --arch i686-linux-gnu + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-linux-gtk-linux-i686 + path: | + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686 + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686.dbg + + build-aarch64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up aarch64 cross-compilation + run: | + mkdir -p $HOME/.ldc/ + cat << EOF | tee $HOME/.ldc/ldc2.conf + "aarch64-.*-linux-gnu": + { + switches = [ + "-defaultlib=phobos2-ldc,druntime-ldc", + "-gcc=aarch64-linux-gnu-gcc", + ]; + post-switches = [ + "-I$HOME/ldc2-1.32.2-linux-aarch64/import", + ]; + lib-dirs = [ + "$HOME/ldc2-1.32.2-linux-aarch64/lib", + ]; + }; + EOF + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.32.2/ldc2-1.32.2-linux-aarch64.tar.xz + tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 -c linux-gtk --arch aarch64-linux-gnu + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-linux-gtk-linux-aarch64 + path: | + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64 + ${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64.dbg diff --git a/dlangui/frontend.d b/dlangui/frontend.d new file mode 100644 index 0000000..cbd94db --- /dev/null +++ b/dlangui/frontend.d @@ -0,0 +1,86 @@ +module frontend; + +import core.runtime; + +import file = std.file; +import std.path; +import std.process; +import std.traits; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +import dlangui; +import dlangui.core.logger; + +import app.frontend; +import constants; +import utils; + +import ui.mainframe; + +version(Windows) { + import logging; + import graphical_app; +} + +extern(C) int DLANGUImain(string[] args); + +shared class DlangUIFrontend: Frontend { + string _configurationPath; + + this() { + version (Windows) { + _configurationPath = environment["LocalAppData"]; + } else version (OSX) { + _configurationPath = "~/Library/Preferences/".expandTilde(); + } else { + _configurationPath = environment.get("XDG_CONFIG_DIR") + .orDefault("~/.config") + .expandTilde(); + } + _configurationPath = _configurationPath.buildPath(applicationName); + } + + override string configurationPath() { + return _configurationPath; + } + + override int run(string[] args) { + version (Windows) { + import core.sys.windows.winbase; + SetUnhandledExceptionFilter(&SIGSEGV_win); + } + + return DLANGUImain(args); + } +} + +Frontend makeFrontend() => new DlangUIFrontend(); + +version(Windows) { + shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared OutputDebugStringLoggingProvider(rootLoggingLevel); +} else { + shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared DefaultProvider(true, rootLoggingLevel); +} + +extern (C) int UIAppMain() +{ + // Most of the time on GNOME, SDL is wrong about DPI. So we just override it. + if (environment.get("XDG_CURRENT_DESKTOP") == "GNOME" && environment.get("XDG_SESSION_TYPE") == "wayland") { + overrideScreenDPI(96); + } + + Log.setStdoutLogger(); + + getLogger().info("Using DlangUI frontend."); + Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 0, 0); + w.resizeWindow(Point(350, 400)); + w.adjustWindowOrContentSize(350, 400); + w.windowOrContentResizeMode = WindowOrContentResizeMode.shrinkWidgets; + w.mainWidget = new MainFrame(); + w.show(); + + return Platform.instance.enterMessageLoop(); +} diff --git a/dlangui/ui/mainframe.d b/dlangui/ui/mainframe.d new file mode 100644 index 0000000..0c3d1a7 --- /dev/null +++ b/dlangui/ui/mainframe.d @@ -0,0 +1,373 @@ +module ui.mainframe; + +import core.thread; + +import std.algorithm; +import std.array; +import std.concurrency; +import std.conv; +import std.format; + +import slf4d; + +import dlangui; +import dlangui.dialogs.filedlg; + +import plist; + +import imobiledevice; + +import constants; +import sideload; +import tools; + +import ui.utils; + +class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ { + string[] devices; + ComboBox deviceBox; + FrameLayout actionsFrame; + VerticalLayout toolsFrame; + + EditLine deviceNameLine; + EditLine modelLine; + EditLine versionLine; + + Application app; + + Observer!string path; + + this() { + auto log = getLogger(); + + layoutWidth = FILL_PARENT; + layoutHeight = FILL_PARENT; + + MenuItem menuItems = new MenuItem(); + { + MenuItem fileItem = new MenuItem(new Action(0, "Account"d)); + { + auto logInAction = new Action(1, "Log-in"d); + logInAction.state = ACTION_STATE_DISABLE; + MenuItem logInItem = new MenuItem(logInAction); + fileItem.add(logInItem); + + MenuItem sep1 = new MenuItem(); + sep1.type = MenuItemType.Separator; + fileItem.add(sep1); + + MenuItem appIdsItem = new MenuItem(new Action(2, "Manage App IDs"d)); // TODO + fileItem.add(appIdsItem); + + MenuItem certificatesItem = new MenuItem(new Action(3, "Manage certificates"d)); // TODO + fileItem.add(certificatesItem); + } + menuItems.add(fileItem); + + MenuItem deviceItem = new MenuItem(new Action(10, "Devices"d)); + { + MenuItem refreshItem = new MenuItem(new Action(11, "Refresh device list"d)); + refreshItem.menuItemAction.connect((_) { + refreshDeviceList(); + return true; + }); + deviceItem.add(refreshItem); + } + menuItems.add(deviceItem); + + MenuItem helpItem = new MenuItem(new Action(20, "Help"d)); + { + MenuItem donateItem = new MenuItem(new Action(21, "Donate"d)); + donateItem.menuItemAction.connect((_) { + import std.process; + browse("https://github.com/sponsors/Dadoum"); + return true; + }); + helpItem.add(donateItem); + + MenuItem aboutItem = new MenuItem(new Action(22, "About"d)); + aboutItem.menuItemAction.connect((_) { + window.showMessageBox( + UIString.fromRaw("About Sideloader"d), + UIString.fromRaw(format!(rawAboutText.to!dstring())(versionStr, "dlangui")) + ); + return true; + }); + helpItem.add(aboutItem); + } + menuItems.add(helpItem); + } + addChild(new MainMenu(menuItems)); + + auto body = new VerticalLayout(); + body.layoutWidth = FILL_PARENT; + body.layoutHeight = FILL_PARENT; + { + deviceBox = new ComboBox(); + deviceBox.itemClick = (_, index) { + string udid = devices[index]; + new Thread({ + auto device = new iDevice(udid); + try { + auto lockdown = new LockdowndClient(device, "sideloader.trust-client"); + setUpTools(device); + updateDeviceInfo(lockdown); + actionsFrame.showChild("ACTIONS"); + } catch (iMobileDeviceException!lockdownd_error_t err) { + log.infoF!"Can't connect to the device: %s"(err.underlyingError); + actionsFrame.showChild("TRUST"); + } catch (Exception ex) { + log.infoF!"Can't connect to the device: %s"(ex); + } + window().invalidate(); + }).start(); + return true; + }; + deviceBox.layoutWidth = FILL_PARENT; + body.addChild(deviceBox); + + actionsFrame = new FrameLayout(); + actionsFrame.layoutWidth = FILL_PARENT; + actionsFrame.layoutHeight = FILL_PARENT; + { + auto trustLabel = new TextWidget("TRUST", "Please unlock your device and trust the computer"d); + trustLabel.alignment = Align.Center; + actionsFrame.addChild(trustLabel); + + auto actions = new TabWidget("ACTIONS"); + actions.layoutWidth = FILL_PARENT; + actions.layoutHeight = FILL_PARENT; + actions.visibility = Visibility.Invisible; + { + auto deviceInfoTable = new TableLayout("INFO"); + deviceInfoTable.layoutWidth = FILL_PARENT; + deviceInfoTable.colCount = 2; + { + deviceInfoTable.addChild(new TextWidget(null, "Device name:"d)); + + deviceNameLine = new EditLine(null, ""d); + deviceNameLine.alignment = Align.VCenter; + deviceNameLine.enabled = false; + deviceNameLine.layoutWidth = FILL_PARENT; + deviceInfoTable.addChild(deviceNameLine); + + deviceInfoTable.addChild(new TextWidget(null, "Device model:"d)); + + modelLine = new EditLine(null, ""d); + modelLine.alignment = Align.VCenter; + modelLine.enabled = false; + modelLine.layoutWidth = FILL_PARENT; + deviceInfoTable.addChild(modelLine); + + deviceInfoTable.addChild(new TextWidget(null, "iOS version:"d)); + + versionLine = new EditLine(null, ""d); + versionLine.alignment = Align.VCenter; + versionLine.enabled = false; + versionLine.layoutWidth = FILL_PARENT; + deviceInfoTable.addChild(versionLine); + } + actions.addTab(deviceInfoTable, "Informations"d); + + auto installFrame = new VerticalLayout("INSTALL"); + installFrame.layoutWidth = FILL_PARENT; + installFrame.layoutHeight = FILL_PARENT; + { + Button installButton; + + auto fileSelectionLayout = new HorizontalLayout(); + fileSelectionLayout.layoutWidth = FILL_PARENT; + { + auto editLine = new EditLine(); + editLine.alignment = Align.VCenter; + editLine.enabled = false; + editLine.layoutWidth = FILL_PARENT; + editLine.text = "Please select an IPA"; + fileSelectionLayout.addChild(editLine); + + auto selectFileButton = new Button(null, "..."d); + selectFileButton.click = (source) { + FileDialog dlg = new FileDialog(UIString.fromRaw("Select IPA"d), window()); + dlg.addFilter(FileFilterEntry(UIString.fromRaw("iOS application package (*.ipa)"d), "*.ipa")); + dlg.dialogResult = (_, result) { + if (result.id != ACTION_OPEN.id) return; + + string selectedPath = dlg.filename(); + editLine.text = selectedPath.to!dstring(); + path = selectedPath; + }; + dlg.show(); + return true; + }; + fileSelectionLayout.addChild(selectFileButton); + } + installFrame.addChild(fileSelectionLayout); + + auto errorLabel = new TextWidget("IPA_ERROR", "Please select an IPA"d); + errorLabel.alignment = Align.Center; + errorLabel.textColor = Color.firebrick; + errorLabel.visibility = Visibility.Invisible; + installFrame.addChild(errorLabel); + + auto appInfoTable = new TableLayout(); + appInfoTable.layoutWidth = FILL_PARENT; + appInfoTable.colCount = 2; + { + appInfoTable.addChild(new TextWidget(null, "Bundle name:"d)); + + auto nameLine = new EditLine(null, ""d); + nameLine.alignment = Align.VCenter; + nameLine.enabled = false; + nameLine.layoutWidth = FILL_PARENT; + appInfoTable.addChild(nameLine); + + appInfoTable.addChild(new TextWidget(null, "Bundle identifier:"d)); + + auto identifierLine = new EditLine(null, ""d); + identifierLine.alignment = Align.VCenter; + identifierLine.enabled = false; + identifierLine.layoutWidth = FILL_PARENT; + appInfoTable.addChild(identifierLine); + + path.connect((newPath) { + try { + app = new Application(newPath); + nameLine.text = app.appInfo["CFBundleName"].str().native().to!dstring(); + identifierLine.text = app.appInfo["CFBundleIdentifier"].str().native().to!dstring(); + errorLabel.visibility = Visibility.Invisible; + installButton.enabled = true; + } catch (Exception ex) { + log.errorF!"Cannot load the app: %s"(ex); + nameLine.text = ""d; + identifierLine.text = ""d; + errorLabel.text = format!"invalid app: %s"d(ex.msg); + errorLabel.visibility = Visibility.Visible; + installButton.enabled = false; + } + }); + } + installFrame.addChild(appInfoTable); + + installFrame.addChild(new VSpacer()); + + installButton = new Button(new Action(101, "Install"d)); + installButton.layoutWidth = FILL_PARENT; + installButton.layoutHeight = WRAP_CONTENT; + installButton.enabled = false; + installFrame.addChild(installButton); + + auto installProgressBar = new ProgressBarWidget(); + installProgressBar.layoutWidth = FILL_PARENT; + installProgressBar.layoutHeight = WRAP_CONTENT; + installFrame.addChild(installProgressBar); + + auto installProgressLabel = new TextWidget(); + installProgressLabel.text = "Idle"d; + installProgressLabel.alignment = Align.Center; + installProgressLabel.layoutWidth = FILL_PARENT; + installProgressLabel.layoutHeight = WRAP_CONTENT; + installFrame.addChild(installProgressLabel); + } + actions.addTab(installFrame, "Sideload"d); + + toolsFrame = new VerticalLayout("TOOLS"); + toolsFrame.layoutWidth = FILL_PARENT; + toolsFrame.layoutHeight = WRAP_CONTENT; + actions.addTab(toolsFrame, "Additional tools"d); + } + actionsFrame.addChild(actions); + } + body.addChild(actionsFrame); + } + addChild(body); + + iDevice.subscribeEvent((ref const(iDeviceEvent) event) { + with (iDeviceEventType) switch (event.event) { + case iDeviceEventType.add: + log.infoF!"Device with UDID %s has been added."(event.udid); + break; + case iDeviceEventType.remove: + log.infoF!"Device with UDID %s has been removed."(event.udid); + break; + case iDeviceEventType.paired: + log.infoF!"Device with UDID %s has been paired."(event.udid); + break; + default: + log.infoF!"Device with UDID %s has been ???? (%s)."(event.udid, event.event); + break; + } + + refreshDeviceList(); + }); + } + + void refreshDeviceList() { + devices = iDevice.deviceList().map!((device) => device.udid).array(); + auto uiDevices = devices.map!((device) => device.to!dstring()).array(); + deviceBox.executeInUiThread({ + deviceBox.items = uiDevices; + + if (uiDevices.length == 0) { + actionsFrame.visibility = Visibility.Invisible; + } else { + actionsFrame.visibility = Visibility.Visible; + } + }); + } + + void updateDeviceInfo(scope LockdowndClient client) { + Plist deviceInfo = client[null, null]; + + deviceNameLine.executeInUiThread({ + deviceNameLine.text = deviceInfo["DeviceName"].str().native().to!dstring(); + }); + + modelLine.executeInUiThread({ + modelLine.text = deviceInfo["HardwareModel"].str().native().to!dstring(); + }); + + versionLine.executeInUiThread({ + versionLine.text = deviceInfo["ProductVersion"].str().native().to!dstring(); + }); + } + + void setUpTools(iDevice device) { + toolsFrame.executeInUiThread({ + toolsFrame.removeAllChildren(); + foreach (tool; toolList(device)) { + auto toolButton = new Button(null, tool.name().to!dstring()); + toolButton.click = (source) { + new Thread({ + auto window = window(); + window.uiTry!({ + tool.run((string message, bool canCancel = true) { + Tid parentTid = thisTid(); + const(Action)[] actions = [ACTION_OK]; + if (canCancel) { + actions ~= ACTION_CANCEL; + } + + window.executeInUiThread({ + window.showMessageBox(""d, message.to!dstring(), actions, 0, (res) { + parentTid.send((res.id != StandardAction.Ok) || !canCancel); + return true; + }); + }); + return receiveOnly!bool(); + }); + }); + }).start(); + return true; + }; + toolButton.layoutWidth = FILL_PARENT; + string diag = tool.diagnostic(); + if (diag) { + toolButton.enabled = false; + toolButton.tooltipText = diag.to!dstring(); + } + + toolsFrame.addChild(toolButton); + } + }); + } +} diff --git a/dlangui/ui/utils.d b/dlangui/ui/utils.d new file mode 100644 index 0000000..aacf38f --- /dev/null +++ b/dlangui/ui/utils.d @@ -0,0 +1,56 @@ +module ui.utils; + +struct Observer(T) { + const(T) obj; + this(T obj, void delegate(T val)[] dels = []) { + this.obj = obj; + this.dels = dels; + } + + void delegate(T val)[] dels; + + size_t connect(void delegate(T val) del) { + auto offset = 0; + while (offset < dels.length) { + if (dels[offset] == null) { + break; + } + } + if (offset >= dels.length) { + dels.length = offset + 1; + } + dels[offset] = del; + return offset; + } + + void disconnect(size_t id) { + dels[id] = null; + } + + scope Observer opAssign(T val) { + foreach (del; dels) { + del(val); + } + return Observer(val, dels); + } + + alias obj this; +} + +import std.conv; +import std.format; + +import slf4d; + +import dlangui; + +void uiTry(alias U)(Window w) { + try { + U(); + } catch (Exception ex) { + getLogger().errorF!"Exception occured: %s"(ex); + w.executeInUiThread({ + w.showMessageBox("Exception occured"d, format!"%s@%s(%s): %s"d(typeid(ex).toString(), ex.file, ex.line, ex.msg)); + }); + } +} \ No newline at end of file diff --git a/dub.json b/dub.json index 598a98a..5ede652 100644 --- a/dub.json +++ b/dub.json @@ -28,8 +28,8 @@ "repository": "git+https://github.com/Dadoum/Provision.git", "version": "533dca306b86f9c7801354b78f5187addb58b740" }, - "requests": "~>2.1.1", - "slf4d": "~>2.4.2" + "requests": "~>2.1", + "slf4d": "~>2" }, "subConfigurations": { @@ -48,8 +48,8 @@ ], "dependencies": { - "gtk_d:gtk": "~>1.0.3", - "gtk_d:adw": "~>1.0.3" + "gtk_d:gtk": "~>1", + "gtk_d:adw": "~>1" } }, { @@ -63,8 +63,8 @@ ], "dependencies": { - "gtk_d:gtk": "~>1.0.3", - "gtk_d:adw": "~>1.0.3" + "gtk_d:gtk": "~>1", + "gtk_d:adw": "~>1" }, "dflags-ldc": [ @@ -81,7 +81,7 @@ "targetType": "executable", "sourcePaths": [ - "windows/common/", + "windows/common", "windows/winforms/" ], @@ -92,6 +92,26 @@ } } }, + { + "name": "dlangui", + "targetType": "executable", + + "sourcePaths": [ + "dlangui/" + ], + + "dependencies": { + "dlangui": "~>0.10" + }, + + "dflags-ldc": [ + "--link-defaultlib-shared=false" + ], + + "dflags-gdc": [ + "-defaultlib=:libgphobos.a" + ] + }, { "name": "cli", "targetType": "executable", diff --git a/dub.selections.json b/dub.selections.json index 37e7835..5bfad49 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -10,6 +10,8 @@ "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, "botan-math": "1.0.4", "cachetools": "0.4.1", + "concepts": "0.0.9", + "concurrency": "5.0.4", "dfl": {"version":"224d9348286620c8ea4854690a09e7380d6f5b2f","repository":"git+https://github.com/Dadoum/dfl.git"}, "dlangui": "0.10.4", "dsfml": "2.1.1", @@ -18,10 +20,12 @@ "glx-d": "1.1.0", "gtk_d": "1.0.3", "icontheme": "1.2.3", + "ikod-containers": "0.0.22", "inilike": "1.2.2", "intel-intrinsics": "1.11.15", "isfreedesktop": "0.1.1", "memutils": "1.0.9", + "mir-core": "1.6.0", "plist": "~master", "plist-d": {"version":"5020d8e45ca2c77183a44ce04053ccbf8bc83262","repository":"git+https://github.com/Dadoum/libplist-d.git"}, "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, diff --git a/linux/gtk/ui/devicewidget.d b/linux/gtk/ui/devicewidget.d index 4c58257..6aa1d90 100644 --- a/linux/gtk/ui/devicewidget.d +++ b/linux/gtk/ui/devicewidget.d @@ -47,6 +47,9 @@ class DeviceWidget: PreferencesGroup { runInUIThread(() { if (phoneExpander) phoneExpander.setTitle(lockdowndClient.deviceName()); }); } catch (iMobileDeviceException!lockdownd_error_t ex) { getLogger().errorF!"Cannot get device name for %s: %s"(deviceId, ex); + if (ex.underlyingError == lockdownd_error_t.LOCKDOWN_E_PASSWORD_PROTECTED) { + runInUIThread(() { if (phoneExpander) phoneExpander.setTitle("(unlock your device)"); }); + } } }).start(); phoneExpander.setSubtitle(deviceId); diff --git a/linux/gtk/ui/toolselectionwindow.d b/linux/gtk/ui/toolselectionwindow.d index bec800f..aa0c44d 100644 --- a/linux/gtk/ui/toolselectionwindow.d +++ b/linux/gtk/ui/toolselectionwindow.d @@ -17,7 +17,6 @@ import gtk.Window; import imobiledevice; import tools; -import tools.sidestorepairingfile; import ui.utils; @@ -38,9 +37,7 @@ class ToolSelectionWindow: Dialog { auto scroll = new ScrolledWindow(); - Tool[] tools = cast(Tool[]) [ - new SideStoreTool(device) - ]; + Tool[] tools = toolList(device); toolListBox = new ListBox(); { foreach (tool; tools) { diff --git a/source/constants.d b/source/constants.d index f149d9b..ff4e9f3 100644 --- a/source/constants.d +++ b/source/constants.d @@ -4,6 +4,14 @@ public import version_string; enum applicationName = "Sideloader"; enum appWebsite = "https://github.com/Dadoum/Sideloader"; +enum rawAboutText = `%s + +Sideloader has been made by Dadoum, with the source code available on https://github.com/Dadoum/Sideloader. + +Sideloader would not have been possible without the work of countless others on different projects. + +Thanks to people behind: libimobiledevice, libplist, Botan, Botan D port, SideStore, AltStore, D Programming language, ` +~ `%s, dlang-requests, slf4d, Cydia Impactor, Apple team behind Apple Music for Android, and many others.`; enum ubyte[] appleAuthCA = cast(ubyte[]) "-----BEGIN CERTIFICATE----- MIID+DCCAuCgAwIBAgIII2l0BK3LgxQwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE diff --git a/source/imobiledevice/package.d b/source/imobiledevice/package.d index 083bac0..5c93677 100644 --- a/source/imobiledevice/package.d +++ b/source/imobiledevice/package.d @@ -19,8 +19,11 @@ import plist; import plist.c; class iMobileDeviceException(T): Exception { + T underlyingError; + this(T error, string file = __FILE__, int line = __LINE__) { super(format!"error %s"(error), file, line); + underlyingError = error; } } @@ -144,7 +147,7 @@ public class LockdowndClient { public Plist opIndex(string domain, string key) { plist_t ret; - lockdownd_get_value(handle, domain.toStringz(), key.toStringz(), &ret).assertSuccess(); + lockdownd_get_value(handle, domain ? domain.toStringz() : null, key ? key.toStringz() : null, &ret).assertSuccess(); return Plist.wrap(ret); } diff --git a/source/sideload/application.d b/source/sideload/application.d index df261c9..08afc37 100644 --- a/source/sideload/application.d +++ b/source/sideload/application.d @@ -58,9 +58,9 @@ class Application: Bundle { } ~this() { - if (tempPath && file.exists(tempPath)) { - file.rmdirRecurse(tempPath); - } + // if (tempPath && file.exists(tempPath)) { + // file.rmdirRecurse(tempPath); + // } } /// Fetches a mobileprovision file for the app diff --git a/source/tools/package.d b/source/tools/package.d index 2aa9d0c..14a803e 100644 --- a/source/tools/package.d +++ b/source/tools/package.d @@ -17,6 +17,13 @@ abstract class Tool { /// Returns null if the action can be performed, otherwise gives a diagnostic on why it is not available. abstract string diagnostic(); - /// Returns success, + /// Returns true if cancelled abstract void run(bool delegate(string message, bool canCancel = true) notify); } + +Tool[] toolList(iDevice device) { + import tools.sidestorepairingfile; + return cast(Tool[]) [ + new SideStoreTool(device), + ]; +} diff --git a/windows/common/graphical_app.d b/windows/common/graphical_app.d new file mode 100644 index 0000000..61b6859 --- /dev/null +++ b/windows/common/graphical_app.d @@ -0,0 +1,19 @@ +module graphical_app; + +import core.sys.windows.windef; + +pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); +static if (__VERSION__ >= 2091) + pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); +else + pragma(linkerDirective, "/ENTRY:mainCRTStartup"); + +private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { + this(string file = __FILE__, size_t line = __LINE__) { + super("Segmentation fault.", file, line); + } +} + +extern (Windows) int SIGSEGV_win(EXCEPTION_POINTERS*) { + throw new SegmentationFault(); // Make an exception to force Windows to generate a stacktrace. +} diff --git a/windows/winforms/logging.d b/windows/common/logging.d similarity index 94% rename from windows/winforms/logging.d rename to windows/common/logging.d index b408546..44e0ac5 100644 --- a/windows/winforms/logging.d +++ b/windows/common/logging.d @@ -12,7 +12,7 @@ import slf4d.provider; class OutputDebugStringLogHandler : LogHandler { public shared void handle(immutable LogMessage msg) { - string logStr = formatLogMessage(msg, false); + string logStr = formatLogMessage(msg, false) ~ "\n"; // if (msg.level.value >= Levels.ERROR.value) { // OutputDebugStringA(logStr.toStringz()); // } else { diff --git a/windows/winforms/frontend.d b/windows/winforms/frontend.d index 9042841..92d4427 100644 --- a/windows/winforms/frontend.d +++ b/windows/winforms/frontend.d @@ -14,6 +14,7 @@ import slf4d.provider; import constants; import app.frontend; +import graphical_app; import logging; import ui.sideloaderform; @@ -45,18 +46,3 @@ shared class WindowsFrontend: Frontend { Frontend makeFrontend() => new WindowsFrontend(); shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared OutputDebugStringLoggingProvider(rootLoggingLevel); -pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); -static if (__VERSION__ >= 2091) - pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); -else - pragma(linkerDirective, "/ENTRY:mainCRTStartup"); - -private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { - this(string file = __FILE__, size_t line = __LINE__) { - super("Segmentation fault.", file, line); - } -} - -extern (Windows) int SIGSEGV_win(EXCEPTION_POINTERS*) { - throw new SegmentationFault(); // Make an exception to force Windows to generate a stacktrace. -} diff --git a/windows/winforms/ui/sideloaderform.d b/windows/winforms/ui/sideloaderform.d index 91f629c..79a047f 100644 --- a/windows/winforms/ui/sideloaderform.d +++ b/windows/winforms/ui/sideloaderform.d @@ -85,14 +85,7 @@ class SideloaderForm: Form { // aboutSideloaderMenuItem // this.aboutSideloaderMenuItem.click ~= (MenuItem menuItem, EventArgs ea) { - msgBox(this, format!`%s - -Sideloader has been made by Dadoum, with the source code available on https://github.com/Dadoum/Sideloader. - -Sideloader would not have been possible without the work of countless others on different projects. - -Thanks to people behind: libimobiledevice, libplist, Botan, Botan D port, SideStore, AltStore, D Programming language, DFL (D Forms Library), dlang-requests, slf4d, Cydia Impactor, Apple team behind Apple Music for Android, and many others.`(versionStr) - ); + msgBox(this, format!rawAboutText(versionStr, "DFL (D Forms Library)")); }; this.aboutSideloaderMenuItem.text = "About Sideloader"; // From 5b1b33cdbc7d2d0d5bad0865dd86a458ad7ea67f Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 00:30:57 +0100 Subject: [PATCH 02/36] Fix Windows compilation in GitHub Actions --- .github/workflows/build-cli.yml | 4 +++- dub.json | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 3ba0e10..68da7f7 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -112,7 +112,9 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + run: | + sudo dpkg --add-architecture arm64 + sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/dub.json b/dub.json index 5ede652..ee22335 100644 --- a/dub.json +++ b/dub.json @@ -100,6 +100,10 @@ "dlangui/" ], + "sourcePaths-windows": [ + "windows/common/" + ], + "dependencies": { "dlangui": "~>0.10" }, From c5f22ec1f03501a71c43a655546996773419dcb9 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 00:32:08 +0100 Subject: [PATCH 03/36] Fix linux-aarch64 compilation in GitHub Actions --- .github/workflows/build-dlangui.yml | 4 +++- .github/workflows/build-gtk.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 7a92861..29f5e65 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -112,7 +112,9 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + run: | + sudo dpkg --add-architecture arm64 + sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 0fab8cd..45bea42 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -112,7 +112,9 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + run: | + sudo dpkg --add-architecture arm64 + sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d From 1a64069d750673acef787038022012fb45a245c2 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 00:45:01 +0100 Subject: [PATCH 04/36] Add arm64 repos to Workflows --- .github/workflows/build-cli.yml | 12 +++++++++++- .github/workflows/build-dlangui.yml | 10 ++++++++++ .github/workflows/build-gtk.yml | 12 +++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 68da7f7..d6641cd 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -113,8 +113,18 @@ jobs: - name: Install dependencies run: | + sudo rm -Rf /etc/apt/sources.list.d + cat << EOF | sudo tee /etc/apt/sources.list + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 29f5e65..606ba65 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -113,6 +113,16 @@ jobs: - name: Install dependencies run: | + sudo rm -Rf /etc/apt/sources.list.d + cat << EOF | sudo tee /etc/apt/sources.list + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + EOF sudo dpkg --add-architecture arm64 sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 45bea42..50f808f 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -113,8 +113,18 @@ jobs: - name: Install dependencies run: | + sudo rm -Rf /etc/apt/sources.list.d + cat << EOF | sudo tee /etc/apt/sources.list + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d From 8362d71c40a701b2d60742f7789b342f4ee33326 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 01:48:33 +0100 Subject: [PATCH 05/36] Fix Windows support for dlangui --- .github/workflows/build-cli.yml | 12 ++++++------ .github/workflows/build-dlangui.yml | 12 ++++++------ .github/workflows/build-gtk.yml | 12 ++++++------ dlangui/frontend.d | 16 +++++++++++++--- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index d6641cd..9526806 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -115,13 +115,13 @@ jobs: run: | sudo rm -Rf /etc/apt/sources.list.d cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe EOF sudo dpkg --add-architecture arm64 sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 606ba65..bc41195 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -115,13 +115,13 @@ jobs: run: | sudo rm -Rf /etc/apt/sources.list.d cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe EOF sudo dpkg --add-architecture arm64 sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 50f808f..2c91f12 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -115,13 +115,13 @@ jobs: run: | sudo rm -Rf /etc/apt/sources.list.d cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME} main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-updates main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu ${DISTRIB_CODENAME}-backports main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME} main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${DISTRIB_CODENAME}-backports main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe EOF sudo dpkg --add-architecture arm64 sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu diff --git a/dlangui/frontend.d b/dlangui/frontend.d index cbd94db..bcde2c8 100644 --- a/dlangui/frontend.d +++ b/dlangui/frontend.d @@ -21,11 +21,17 @@ import utils; import ui.mainframe; version(Windows) { + import core.sys.windows.winbase; + import core.sys.windows.winuser; + import logging; import graphical_app; -} -extern(C) int DLANGUImain(string[] args); + extern(C) int DLANGUIWinMain(void* hInstance, void* hPrevInstance, + char* lpCmdLine, int nCmdShow); +} else { + extern(C) int DLANGUImain(string[] args); +} shared class DlangUIFrontend: Frontend { string _configurationPath; @@ -53,7 +59,11 @@ shared class DlangUIFrontend: Frontend { SetUnhandledExceptionFilter(&SIGSEGV_win); } - return DLANGUImain(args); + version (Windows) { + return DLANGUIWinMain(GetModuleHandle(null), null, null, SW_SHOWNORMAL); + } else { + return DLANGUImain(args); + } } } From a70a286bbd75399bfcab192173b3851289e62ceb Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 01:56:37 +0100 Subject: [PATCH 06/36] Please fix ARM64 build --- .github/workflows/build-dlangui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index bc41195..41645d7 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -124,7 +124,7 @@ jobs: deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d From d682c29342a591a2bda5f97c7be66470d7e700e1 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 1 Nov 2023 02:06:29 +0100 Subject: [PATCH 07/36] ... --- .github/workflows/build-dlangui.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 41645d7..09129b1 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -119,12 +119,14 @@ jobs: deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse + deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d From fb731ee62664326320508efaecab2a91640549ea Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 2 Nov 2023 23:14:27 +0100 Subject: [PATCH 08/36] Refactor the frontends as subpackages --- .github/workflows/build-cli.yml | 32 +-- .github/workflows/build-dlangui.yml | 14 +- .github/workflows/build-gtk.yml | 235 ++++++++++++++++-- cli/frontend.d | 107 -------- dlangui/frontend.d | 96 ------- dub.json | 102 +------- frontends/cli/dub.json | 21 ++ frontends/cli/dub.selections.json | 21 ++ frontends/cli/source/main.d | 109 ++++++++ .../common/linux}/secret/BackendIF.d | 0 .../common/linux}/secret/BackendT.d | 0 .../common/linux}/secret/Collection.d | 0 .../common/linux}/secret/Item.d | 0 .../common/linux}/secret/Prompt.d | 0 .../common/linux}/secret/RetrievableIF.d | 0 .../common/linux}/secret/RetrievableT.d | 0 .../common/linux}/secret/Schema.d | 0 .../common/linux}/secret/SchemaAttribute.d | 0 .../common/linux}/secret/Service.d | 0 .../common/linux}/secret/Value.d | 0 .../common/linux}/secret/c/functions.d | 0 .../common/linux}/secret/c/types.d | 0 .../common/linux}/secretapi.d | 0 .../common/windows}/graphical_app.d | 1 + .../common/windows}/logging.d | 0 frontends/dlangui/dub.json | 32 +++ frontends/dlangui/dub.selections.json | 34 +++ frontends/dlangui/source/main.d | 76 ++++++ frontends/dlangui/source/ui/loginframe.d | 125 ++++++++++ .../dlangui/source}/ui/mainframe.d | 13 +- frontends/dlangui/source/ui/tfaframe.d | 20 ++ .../dlangui/source}/ui/utils.d | 0 frontends/gtk/dub.json | 43 ++++ frontends/gtk/dub.selections.json | 22 ++ frontends/gtk/source/main.d | 83 +++++++ .../ui/authentication/assistantslide.d | 0 .../authentication/authenticationassistant.d | 0 .../source}/ui/authentication/loginslide.d | 0 .../gtk/source}/ui/authentication/tfaslide.d | 0 .../gtk/source}/ui/dependencieswindow.d | 4 +- .../gtk/source}/ui/devicewidget.d | 0 .../gtk/source}/ui/mainwindow.d | 0 .../gtk/source}/ui/manageappidwindow.d | 0 .../gtk/source}/ui/managecertificateswindow.d | 0 .../gtk/source}/ui/numberentry.d | 0 .../gtk/source}/ui/sideloadergtkapplication.d | 16 +- .../gtk/source}/ui/sideloadprogresswindow.d | 2 +- .../gtk/source}/ui/toolselectionwindow.d | 0 .../gtk => frontends/gtk/source}/ui/utils.d | 0 frontends/swiftui/dub.json | 27 ++ frontends/swiftui/dub.selections.json | 21 ++ .../swiftui/source/objc/developerservices.d | 22 ++ linux/gtk/frontend.d | 77 ------ source/app/frontend.d | 105 -------- source/app/package.d | 103 ++++++++ source/imobiledevice/package.d | 4 +- source/main.d | 52 ---- source/sideload/package.d | 5 +- windows/winforms/frontend.d | 48 ---- windows/winforms/ui/dependenciesform.d | 103 -------- windows/winforms/ui/sideloaderform.d | 194 --------------- 61 files changed, 1029 insertions(+), 940 deletions(-) delete mode 100644 cli/frontend.d delete mode 100644 dlangui/frontend.d create mode 100644 frontends/cli/dub.json create mode 100644 frontends/cli/dub.selections.json create mode 100644 frontends/cli/source/main.d rename {linux/common => frontends/common/linux}/secret/BackendIF.d (100%) rename {linux/common => frontends/common/linux}/secret/BackendT.d (100%) rename {linux/common => frontends/common/linux}/secret/Collection.d (100%) rename {linux/common => frontends/common/linux}/secret/Item.d (100%) rename {linux/common => frontends/common/linux}/secret/Prompt.d (100%) rename {linux/common => frontends/common/linux}/secret/RetrievableIF.d (100%) rename {linux/common => frontends/common/linux}/secret/RetrievableT.d (100%) rename {linux/common => frontends/common/linux}/secret/Schema.d (100%) rename {linux/common => frontends/common/linux}/secret/SchemaAttribute.d (100%) rename {linux/common => frontends/common/linux}/secret/Service.d (100%) rename {linux/common => frontends/common/linux}/secret/Value.d (100%) rename {linux/common => frontends/common/linux}/secret/c/functions.d (100%) rename {linux/common => frontends/common/linux}/secret/c/types.d (100%) rename {linux/common => frontends/common/linux}/secretapi.d (100%) rename {windows/common => frontends/common/windows}/graphical_app.d (89%) rename {windows/common => frontends/common/windows}/logging.d (100%) create mode 100644 frontends/dlangui/dub.json create mode 100644 frontends/dlangui/dub.selections.json create mode 100644 frontends/dlangui/source/main.d create mode 100644 frontends/dlangui/source/ui/loginframe.d rename {dlangui => frontends/dlangui/source}/ui/mainframe.d (96%) create mode 100644 frontends/dlangui/source/ui/tfaframe.d rename {dlangui => frontends/dlangui/source}/ui/utils.d (100%) create mode 100644 frontends/gtk/dub.json create mode 100644 frontends/gtk/dub.selections.json create mode 100644 frontends/gtk/source/main.d rename {linux/gtk => frontends/gtk/source}/ui/authentication/assistantslide.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/authentication/authenticationassistant.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/authentication/loginslide.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/authentication/tfaslide.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/dependencieswindow.d (96%) rename {linux/gtk => frontends/gtk/source}/ui/devicewidget.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/mainwindow.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/manageappidwindow.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/managecertificateswindow.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/numberentry.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/sideloadergtkapplication.d (95%) rename {linux/gtk => frontends/gtk/source}/ui/sideloadprogresswindow.d (97%) rename {linux/gtk => frontends/gtk/source}/ui/toolselectionwindow.d (100%) rename {linux/gtk => frontends/gtk/source}/ui/utils.d (100%) create mode 100644 frontends/swiftui/dub.json create mode 100644 frontends/swiftui/dub.selections.json create mode 100644 frontends/swiftui/source/objc/developerservices.d delete mode 100644 linux/gtk/frontend.d delete mode 100644 source/app/frontend.d create mode 100644 source/app/package.d delete mode 100644 source/main.d delete mode 100644 windows/winforms/frontend.d delete mode 100644 windows/winforms/ui/dependenciesform.d delete mode 100644 windows/winforms/ui/sideloaderform.d diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 9526806..4e6aafd 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -25,7 +25,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli + run: dub build -b release-debug --compiler=ldc2 :cli-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-x86_64" @@ -41,8 +41,6 @@ jobs: ${{github.workspace}}/bin/sideloader-cli-linux-x86_64.dbg build-i686: - # Does not work yet - if: false runs-on: ubuntu-22.04 steps: @@ -63,7 +61,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli --arch i686-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch i686-linux-gnu :cli-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-i686" @@ -115,22 +113,24 @@ jobs: run: | sudo rm -Rf /etc/apt/sources.list.d cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main universe - - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy + + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse + deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli --arch aarch64-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch aarch64-linux-gnu :cli-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-linux-aarch64" @@ -196,7 +196,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli --arch x86_64-apple-darwin + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-apple-darwin :cli-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-macOS-x86_64" # TODO make an app bundle @@ -258,7 +258,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli --arch arm64-apple-macos + run: dub build -b release-debug --compiler=ldc2 --arch arm64-apple-macos :cli-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-cli-macOS-arm64" # TODO make an app bundle @@ -311,7 +311,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c cli --arch x86_64-windows-msvc + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-windows-msvc :cli-frontend - name: Rename run: | diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 09129b1..5ceb23c 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -25,7 +25,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui + run: dub build -b release-debug --compiler=ldc2 :dlangui-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-x86_64" @@ -63,7 +63,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch i686-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch i686-linux-gnu :dlangui-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-i686" @@ -79,6 +79,8 @@ jobs: ${{github.workspace}}/bin/sideloader-dlangui-linux-i686.dbg build-aarch64: + # Does not work yet + if: false runs-on: ubuntu-22.04 steps: @@ -132,7 +134,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch aarch64-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch aarch64-linux-gnu :dlangui-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-linux-aarch64" @@ -198,7 +200,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch x86_64-apple-darwin + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-apple-darwin :dlangui-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-macOS-x86_64" # TODO make an app bundle @@ -260,7 +262,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch arm64-apple-macos + run: dub build -b release-debug --compiler=ldc2 --arch arm64-apple-macos :dlangui-frontend - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-dlangui-macOS-arm64" # TODO make an app bundle @@ -313,7 +315,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c dlangui --arch x86_64-windows-msvc + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-windows-msvc :dlangui-frontend - name: Rename run: | diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 2c91f12..ac64070 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -25,20 +25,20 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk + run: dub build -b release-debug --compiler=ldc2 :gtk-frontend - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-gtk-linux-x86_64" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-gtk-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-gtk-linux-x86_64" - uses: actions/upload-artifact@v3 with: - name: sideloader-linux-gtk-linux-x86_64 + name: sideloader-gtk-linux-x86_64 path: | - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64 - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-x86_64.dbg + ${{github.workspace}}/bin/sideloader-gtk-linux-x86_64 + ${{github.workspace}}/bin/sideloader-gtk-linux-x86_64.dbg build-i686: # Does not work yet @@ -63,20 +63,20 @@ jobs: run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk --arch i686-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch i686-linux-gnu :gtk-frontend - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-gtk-linux-i686" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-gtk-linux-i686.dbg" "${{github.workspace}}/bin/sideloader-gtk-linux-i686" - uses: actions/upload-artifact@v3 with: - name: sideloader-linux-gtk-linux-i686 + name: sideloader-gtk-linux-i686 path: | - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686 - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-i686.dbg + ${{github.workspace}}/bin/sideloader-gtk-linux-i686 + ${{github.workspace}}/bin/sideloader-gtk-linux-i686.dbg build-aarch64: runs-on: ubuntu-22.04 @@ -115,32 +115,217 @@ jobs: run: | sudo rm -Rf /etc/apt/sources.list.d cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main universe - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main universe + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy + deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main universe + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse + deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse + deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner EOF sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-aarch64-linux-gnu + sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 -c linux-gtk --arch aarch64-linux-gnu + run: dub build -b release-debug --compiler=ldc2 --arch aarch64-linux-gnu :gtk-frontend - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64" + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-gtk-linux-aarch64" - name: Put debug symbols in a separate file - run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64.dbg" "${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64" + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-gtk-linux-aarch64.dbg" "${{github.workspace}}/bin/sideloader-gtk-linux-aarch64" - uses: actions/upload-artifact@v3 with: - name: sideloader-linux-gtk-linux-aarch64 + name: sideloader-gtk-linux-aarch64 path: | - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64 - ${{github.workspace}}/bin/sideloader-linux-gtk-linux-aarch64.dbg + ${{github.workspace}}/bin/sideloader-gtk-linux-aarch64 + ${{github.workspace}}/bin/sideloader-gtk-linux-aarch64.dbg + + build-macos-x86_64: + if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "x86_64-apple-darwin": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld", + "-Xcc=-target", + "-Xcc=x86_64-apple-darwin", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-x86_64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-x86_64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-x86_64.tar.xz + tar -xf ./ldc2-1.33.0-osx-x86_64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-apple-darwin :gtk-frontend + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-gtk-macOS-x86_64" # TODO make an app bundle + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-gtk-macOS-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-gtk-macOS-x86_64 + + build-macos-arm64: + if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "arm64-apple-macos": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld", + "-Xcc=-target", + "-Xcc=arm64-apple-macos", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-arm64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-arm64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-arm64.tar.xz + tar -xf ./ldc2-1.33.0-osx-arm64.tar.xz -C $HOME + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 --arch arm64-apple-macos :gtk-frontend + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-gtk-macOS-arm64" # TODO make an app bundle + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-gtk-macOS-arm64 + path: | + ${{github.workspace}}/bin/sideloader-gtk-macOS-arm64 + + build-windows-x86_64: + if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld 7zip + + - name: Set-up Windows cross-compilation + run: | + mkdir -p $HOME/.ldc/ + cat << EOF | tee $HOME/.ldc/ldc2.conf + "x86_64-.*-windows-msvc": + { + // default switches injected before all explicit command-line switches + switches = [ + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-windows-x64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-windows-x64/lib", + ]; + }; + EOF + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-windows-x64.7z + 7z x ./ldc2-1.33.0-windows-x64.7z -o$HOME + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-windows-msvc :gtk-frontend + + - name: Rename + run: | + mv "${{github.workspace}}/bin/sideloader.exe" "${{github.workspace}}/bin/sideloader-gtk-windows-x86_64.exe" + mv "${{github.workspace}}/bin/sideloader.pdb" "${{github.workspace}}/bin/sideloader-gtk-windows-x86_64.pdb" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-gtk-windows-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-gtk-windows-x86_64.exe + ${{github.workspace}}/bin/sideloader-gtk-windows-x86_64.pdb diff --git a/cli/frontend.d b/cli/frontend.d deleted file mode 100644 index 1685c50..0000000 --- a/cli/frontend.d +++ /dev/null @@ -1,107 +0,0 @@ -module frontend; - -import std.algorithm; -import std.array; -import std.datetime; -import std.format; -import std.path; -import std.stdio; -import std.sumtype; -import std.typecons; -import file = std.file; - -import slf4d; -import slf4d.default_provider; -import slf4d.provider; - -import plist; - -import imobiledevice; - -import server.appleaccount; -import server.developersession; - -import sideload; -import sideload.bundle; -import sideload.application; -import sideload.certificateidentity; - -import app.frontend; -import main; - -version = X509; -shared class CLIFrontend: Frontend { - override string configurationPath() { - return expandTilde("./sideloader-config"); - } - - override int run(string[] args) { - auto log = getLogger(); - - string appPath; - - if (args.length != 2) { - log.errorF!"Usage: %s "(args.length ? args[0] : "sideloader"); - return 1; - } - appPath = args[1]; - - if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { - auto succeeded = downloadAndInstallDeps((progress) { - write(format!"%.2f %% completed\r"(progress * 100)); - stdout.flush(); - - return false; - }); - - if (!succeeded) { - log.error("Download failed."); - return 1; - } - log.info("Download completed."); - } - - initializeADI(); - scope app = new Application(appPath); - - write("Enter your Apple ID: "); - stdout.flush(); - string appleId = readln()[0..$ - 1]; - write("Enter your password (will appear in clear in your terminal): "); - stdout.flush(); - string password = readln()[0..$ - 1]; - - DeveloperSession appleAccount = DeveloperSession.login( - device, - adi, - appleId, - password, - (sendCode, submitCode) { - sendCode(); - write("A code has been sent to your devices, please write it here: "); - stdout.flush(); - string code = readln(); - submitCode(code); - }).match!( - (DeveloperSession session) => session, - (AppleLoginError error) { - auto errorStr = format!"%s (%d)"(error.description, error); - getLogger().errorF!"Apple auth error: %s"(errorStr); - return null; - } - ); - - if (appleAccount) { - string udid = iDevice.deviceList()[0].udid; - log.infoF!"Initiating connection the device (UUID: %s)"(udid); - auto device = new iDevice(udid); - sideloadFull(device, appleAccount, app, (progress, action) { - log.infoF!"%s (%.2f%%)"(action, progress * 100); - }); - } - return 0; - } -} - -Frontend makeFrontend() => new CLIFrontend(); -shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared DefaultProvider(true, rootLoggingLevel); diff --git a/dlangui/frontend.d b/dlangui/frontend.d deleted file mode 100644 index bcde2c8..0000000 --- a/dlangui/frontend.d +++ /dev/null @@ -1,96 +0,0 @@ -module frontend; - -import core.runtime; - -import file = std.file; -import std.path; -import std.process; -import std.traits; - -import slf4d; -import slf4d.default_provider; -import slf4d.provider; - -import dlangui; -import dlangui.core.logger; - -import app.frontend; -import constants; -import utils; - -import ui.mainframe; - -version(Windows) { - import core.sys.windows.winbase; - import core.sys.windows.winuser; - - import logging; - import graphical_app; - - extern(C) int DLANGUIWinMain(void* hInstance, void* hPrevInstance, - char* lpCmdLine, int nCmdShow); -} else { - extern(C) int DLANGUImain(string[] args); -} - -shared class DlangUIFrontend: Frontend { - string _configurationPath; - - this() { - version (Windows) { - _configurationPath = environment["LocalAppData"]; - } else version (OSX) { - _configurationPath = "~/Library/Preferences/".expandTilde(); - } else { - _configurationPath = environment.get("XDG_CONFIG_DIR") - .orDefault("~/.config") - .expandTilde(); - } - _configurationPath = _configurationPath.buildPath(applicationName); - } - - override string configurationPath() { - return _configurationPath; - } - - override int run(string[] args) { - version (Windows) { - import core.sys.windows.winbase; - SetUnhandledExceptionFilter(&SIGSEGV_win); - } - - version (Windows) { - return DLANGUIWinMain(GetModuleHandle(null), null, null, SW_SHOWNORMAL); - } else { - return DLANGUImain(args); - } - } -} - -Frontend makeFrontend() => new DlangUIFrontend(); - -version(Windows) { - shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared OutputDebugStringLoggingProvider(rootLoggingLevel); -} else { - shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared DefaultProvider(true, rootLoggingLevel); -} - -extern (C) int UIAppMain() -{ - // Most of the time on GNOME, SDL is wrong about DPI. So we just override it. - if (environment.get("XDG_CURRENT_DESKTOP") == "GNOME" && environment.get("XDG_SESSION_TYPE") == "wayland") { - overrideScreenDPI(96); - } - - Log.setStdoutLogger(); - - getLogger().info("Using DlangUI frontend."); - Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 0, 0); - w.resizeWindow(Point(350, 400)); - w.adjustWindowOrContentSize(350, 400); - w.windowOrContentResizeMode = WindowOrContentResizeMode.shrinkWidgets; - w.mainWidget = new MainFrame(); - w.show(); - - return Platform.instance.enterMessageLoop(); -} diff --git a/dub.json b/dub.json index ee22335..99f3e7b 100644 --- a/dub.json +++ b/dub.json @@ -5,7 +5,7 @@ "Dadoum" ], - "targetPath": "bin/", + "targetType": "sourceLibrary", "stringImportPaths": ["resources/"], "buildRequirements": ["allowWarnings", "requireBoundsCheck"], @@ -36,101 +36,9 @@ "provision": "libplist" }, - "configurations": [ - { - "name": "linux-gtk", - "platforms": ["linux"], - "targetType": "executable", - - "sourcePaths": [ - "linux/common/", - "linux/gtk/" - ], - - "dependencies": { - "gtk_d:gtk": "~>1", - "gtk_d:adw": "~>1" - } - }, - { - "name": "linux-gtk-static", - "platforms": ["linux"], - "targetType": "executable", - - "sourcePaths": [ - "linux/common/", - "linux/gtk/" - ], - - "dependencies": { - "gtk_d:gtk": "~>1", - "gtk_d:adw": "~>1" - }, - - "dflags-ldc": [ - "--link-defaultlib-shared=false" - ], - - "dflags-gdc": [ - "-defaultlib=:libgphobos.a" - ] - }, - { - "name": "windows-winforms", - "platforms": ["windows"], - "targetType": "executable", - - "sourcePaths": [ - "windows/common", - "windows/winforms/" - ], - - "dependencies": { - "dfl": { - "repository": "git+https://github.com/Dadoum/dfl.git", - "version": "224d9348286620c8ea4854690a09e7380d6f5b2f" - } - } - }, - { - "name": "dlangui", - "targetType": "executable", - - "sourcePaths": [ - "dlangui/" - ], - - "sourcePaths-windows": [ - "windows/common/" - ], - - "dependencies": { - "dlangui": "~>0.10" - }, - - "dflags-ldc": [ - "--link-defaultlib-shared=false" - ], - - "dflags-gdc": [ - "-defaultlib=:libgphobos.a" - ] - }, - { - "name": "cli", - "targetType": "executable", - - "sourcePaths": [ - "cli/" - ], - - "dflags-ldc": [ - "--link-defaultlib-shared=false" - ], - - "dflags-gdc": [ - "-defaultlib=:libgphobos.a" - ] - } + "subPackages": [ + "frontends/cli", + "frontends/dlangui", + "frontends/gtk" ] } \ No newline at end of file diff --git a/frontends/cli/dub.json b/frontends/cli/dub.json new file mode 100644 index 0000000..b3e95e6 --- /dev/null +++ b/frontends/cli/dub.json @@ -0,0 +1,21 @@ +{ + "name": "cli-frontend", + "targetType": "executable", + "targetPath": "../../bin/", + "targetName": "sideloader", + + "sourcePaths": [ + "source/" + ], + + "dependencies": { + "sideloader": { "path": "../../" } + }, + "dflags-ldc": [ + "--link-defaultlib-shared=false" + ], + + "dflags-gdc": [ + "-defaultlib=:libgphobos.a" + ] +} \ No newline at end of file diff --git a/frontends/cli/dub.selections.json b/frontends/cli/dub.selections.json new file mode 100644 index 0000000..2eea410 --- /dev/null +++ b/frontends/cli/dub.selections.json @@ -0,0 +1,21 @@ +{ + "fileVersion": 1, + "versions": { + "automem": "0.6.9", + "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, + "botan-math": "1.0.4", + "cachetools": "0.4.1", + "dxml": "0.4.4", + "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "intel-intrinsics": "1.11.15", + "memutils": "1.0.9", + "plist": "~master", + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, + "requests": "2.1.1", + "sideloader": {"path":"../../"}, + "slf4d": "2.4.3", + "test_allocator": "0.3.4", + "unit-threaded": "0.10.8" + } +} diff --git a/frontends/cli/source/main.d b/frontends/cli/source/main.d new file mode 100644 index 0000000..af13cd8 --- /dev/null +++ b/frontends/cli/source/main.d @@ -0,0 +1,109 @@ +module main; + +import std.algorithm; +import std.array; +import std.datetime; +import std.format; +import std.path; +import std.stdio; +import std.sumtype; +import std.typecons; +import file = std.file; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +import plist; + +import imobiledevice; + +import server.appleaccount; +import server.developersession; + +import sideload; +import sideload.bundle; +import sideload.application; +import sideload.certificateidentity; + +import app; + +version = X509; + +int main(string[] args) { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + auto configurationPath = "./sideloader-config"; + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string appPath; + + if (args.length != 2) { + log.errorF!"Usage: %s "(args.length ? args[0] : "sideloader"); + return 1; + } + appPath = args[1]; + + if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { + auto succeeded = downloadAndInstallDeps(configurationPath, (progress) { + write(format!"%.2f %% completed\r"(progress * 100)); + stdout.flush(); + + return false; + }); + + if (!succeeded) { + log.error("Download failed."); + return 1; + } + log.info("Download completed."); + } + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + scope app = new Application(appPath); + + write("Enter your Apple ID: "); + stdout.flush(); + string appleId = readln()[0..$ - 1]; + write("Enter your password (will appear in clear in your terminal): "); + stdout.flush(); + string password = readln()[0..$ - 1]; + + DeveloperSession appleAccount = DeveloperSession.login( + akDevice, + adi, + appleId, + password, + (sendCode, submitCode) { + sendCode(); + write("A code has been sent to your devices, please write it here: "); + stdout.flush(); + string code = readln(); + submitCode(code); + }).match!( + (DeveloperSession session) => session, + (AppleLoginError error) { + auto errorStr = format!"%s (%d)"(error.description, error); + getLogger().errorF!"Apple auth error: %s"(errorStr); + return null; + } + ); + + if (appleAccount) { + string udid = iDevice.deviceList()[0].udid; + log.infoF!"Initiating connection the device (UUID: %s)"(udid); + auto device = new iDevice(udid); + sideloadFull(configurationPath, device, appleAccount, app, (progress, action) { + log.infoF!"%s (%.2f%%)"(action, progress * 100); + }); + } + + return 0; +} diff --git a/linux/common/secret/BackendIF.d b/frontends/common/linux/secret/BackendIF.d similarity index 100% rename from linux/common/secret/BackendIF.d rename to frontends/common/linux/secret/BackendIF.d diff --git a/linux/common/secret/BackendT.d b/frontends/common/linux/secret/BackendT.d similarity index 100% rename from linux/common/secret/BackendT.d rename to frontends/common/linux/secret/BackendT.d diff --git a/linux/common/secret/Collection.d b/frontends/common/linux/secret/Collection.d similarity index 100% rename from linux/common/secret/Collection.d rename to frontends/common/linux/secret/Collection.d diff --git a/linux/common/secret/Item.d b/frontends/common/linux/secret/Item.d similarity index 100% rename from linux/common/secret/Item.d rename to frontends/common/linux/secret/Item.d diff --git a/linux/common/secret/Prompt.d b/frontends/common/linux/secret/Prompt.d similarity index 100% rename from linux/common/secret/Prompt.d rename to frontends/common/linux/secret/Prompt.d diff --git a/linux/common/secret/RetrievableIF.d b/frontends/common/linux/secret/RetrievableIF.d similarity index 100% rename from linux/common/secret/RetrievableIF.d rename to frontends/common/linux/secret/RetrievableIF.d diff --git a/linux/common/secret/RetrievableT.d b/frontends/common/linux/secret/RetrievableT.d similarity index 100% rename from linux/common/secret/RetrievableT.d rename to frontends/common/linux/secret/RetrievableT.d diff --git a/linux/common/secret/Schema.d b/frontends/common/linux/secret/Schema.d similarity index 100% rename from linux/common/secret/Schema.d rename to frontends/common/linux/secret/Schema.d diff --git a/linux/common/secret/SchemaAttribute.d b/frontends/common/linux/secret/SchemaAttribute.d similarity index 100% rename from linux/common/secret/SchemaAttribute.d rename to frontends/common/linux/secret/SchemaAttribute.d diff --git a/linux/common/secret/Service.d b/frontends/common/linux/secret/Service.d similarity index 100% rename from linux/common/secret/Service.d rename to frontends/common/linux/secret/Service.d diff --git a/linux/common/secret/Value.d b/frontends/common/linux/secret/Value.d similarity index 100% rename from linux/common/secret/Value.d rename to frontends/common/linux/secret/Value.d diff --git a/linux/common/secret/c/functions.d b/frontends/common/linux/secret/c/functions.d similarity index 100% rename from linux/common/secret/c/functions.d rename to frontends/common/linux/secret/c/functions.d diff --git a/linux/common/secret/c/types.d b/frontends/common/linux/secret/c/types.d similarity index 100% rename from linux/common/secret/c/types.d rename to frontends/common/linux/secret/c/types.d diff --git a/linux/common/secretapi.d b/frontends/common/linux/secretapi.d similarity index 100% rename from linux/common/secretapi.d rename to frontends/common/linux/secretapi.d diff --git a/windows/common/graphical_app.d b/frontends/common/windows/graphical_app.d similarity index 89% rename from windows/common/graphical_app.d rename to frontends/common/windows/graphical_app.d index 61b6859..427321a 100644 --- a/windows/common/graphical_app.d +++ b/frontends/common/windows/graphical_app.d @@ -1,6 +1,7 @@ module graphical_app; import core.sys.windows.windef; +public import core.sys.windows.winbase: SetUnhandledExceptionFilter; pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); static if (__VERSION__ >= 2091) diff --git a/windows/common/logging.d b/frontends/common/windows/logging.d similarity index 100% rename from windows/common/logging.d rename to frontends/common/windows/logging.d diff --git a/frontends/dlangui/dub.json b/frontends/dlangui/dub.json new file mode 100644 index 0000000..c358082 --- /dev/null +++ b/frontends/dlangui/dub.json @@ -0,0 +1,32 @@ +{ + "name": "dlangui-frontend", + "targetType": "executable", + "targetPath": "../../bin/", + "targetName": "sideloader", + + "sourcePaths": [ + "source/" + ], + + "sourcePaths-windows": [ + "../common/windows/" + ], + + "dependencies": { + "sideloader": { "path": "../../" }, + "dlangui": "~>0.10" + }, + + "dflags-ldc": [ + "--link-defaultlib-shared=false" + ], + + "dflags-gdc": [ + "-defaultlib=:libgphobos.a" + ], + + "lflags-osx": [ + "-rpath", + "@loader_path/../Frameworks" + ] +} \ No newline at end of file diff --git a/frontends/dlangui/dub.selections.json b/frontends/dlangui/dub.selections.json new file mode 100644 index 0000000..89823fb --- /dev/null +++ b/frontends/dlangui/dub.selections.json @@ -0,0 +1,34 @@ +{ + "fileVersion": 1, + "versions": { + "arsd-official": "10.9.10", + "automem": "0.6.9", + "bindbc-freetype": "1.0.5", + "bindbc-loader": "1.0.3", + "bindbc-opengl": "1.0.5", + "bindbc-sdl": "1.0.1", + "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, + "botan-math": "1.0.4", + "cachetools": "0.4.1", + "dlangui": "0.10.4", + "dsfml": "2.1.1", + "dxml": "0.4.4", + "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "glx-d": "1.1.0", + "icontheme": "1.2.3", + "inilike": "1.2.2", + "intel-intrinsics": "1.11.15", + "isfreedesktop": "0.1.1", + "memutils": "1.0.9", + "plist": "~master", + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, + "requests": "2.1.1", + "sideloader": {"path":"../../"}, + "slf4d": "2.4.3", + "test_allocator": "0.3.4", + "unit-threaded": "0.10.8", + "x11": "1.0.21", + "xdgpaths": "0.2.5" + } +} diff --git a/frontends/dlangui/source/main.d b/frontends/dlangui/source/main.d new file mode 100644 index 0000000..77a086f --- /dev/null +++ b/frontends/dlangui/source/main.d @@ -0,0 +1,76 @@ +module main; + +import core.runtime; + +import file = std.file; +import std.path; +import std.process; +import std.traits; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +import dlangui; +import dlangui.core.logger; + +import app; +import constants; +import utils; + +import ui.mainframe; + +mixin APP_ENTRY_POINT; + +extern (C) int UIAppMain() { + debug { + Level level = Levels.TRACE; + } else { + Level level = Levels.INFO; + } + + import core.thread; + import imobiledevice; + new Thread({ + import tools; + auto device = new iDevice(iDevice.deviceList()[0].udid); + auto lockdown = new LockdowndClient(device, "sideloader.trust-client"); + toolList(device); + }).start(); + + version(Windows) { + import graphical_app; + SetUnhandledExceptionFilter(&SIGSEGV_win); + + import logging; + auto loggingProvider = new shared OutputDebugStringLoggingProvider(level); + } else { + auto loggingProvider = new shared DefaultProvider(true, level); + } + + version (Windows) { + string configurationPath = environment["LocalAppData"]; + } else version (OSX) { + string configurationPath = "~/Library/Preferences/".expandTilde(); + } else { + string configurationPath = environment.get("XDG_CONFIG_DIR") + .orDefault("~/.config") + .expandTilde(); + } + configurationPath = configurationPath.buildPath(applicationName); + + // Most of the time on GNOME, SDL is wrong about DPI. So we just override it. + if (environment.get("XDG_CURRENT_DESKTOP") == "GNOME" && environment.get("XDG_SESSION_TYPE") == "wayland") { + overrideScreenDPI(96); + } + + Log.setStdoutLogger(); + + getLogger().info("Using DlangUI frontend."); + Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 350, 400); + w.mainWidget = new MainFrame(); + w.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + w.show(); + + return Platform.instance.enterMessageLoop(); +} diff --git a/frontends/dlangui/source/ui/loginframe.d b/frontends/dlangui/source/ui/loginframe.d new file mode 100644 index 0000000..50ebbc3 --- /dev/null +++ b/frontends/dlangui/source/ui/loginframe.d @@ -0,0 +1,125 @@ +module ui.loginframe; + +import core.thread; + +import std.conv; +import std.format; +import std.sumtype; + +import dlangui; + +import provision; + +import server.appleaccount; +import server.developersession; + +import ui.tfaframe; + +class LoginFrame: VerticalLayout { + EditLine usernameLine; + EditLine passwordLine; + + this(Device device, ADI adi, void delegate(DeveloperSession) onCompletion) { + auto errorLabel = new TextWidget("LOGIN_ERROR", ""d); + errorLabel.alignment = Align.Center; + errorLabel.textColor = Color.firebrick; + errorLabel.visibility = Visibility.Invisible; + addChild(errorLabel); + + auto credentialsTable = new TableLayout("CREDS"); + credentialsTable.layoutWidth = FILL_PARENT; + credentialsTable.colCount = 2; + { + credentialsTable.addChild(new TextWidget(null, "Apple ID:"d)); + + usernameLine = new EditLine("USERNAME", ""d); + usernameLine.alignment = Align.VCenter; + usernameLine.layoutWidth = FILL_PARENT; + credentialsTable.addChild(usernameLine); + + credentialsTable.addChild(new TextWidget(null, "Password:"d)); + + passwordLine = new EditLine("PASSWORD", ""d); + passwordLine.passwordChar = '\u2022'; + passwordLine.alignment = Align.VCenter; + passwordLine.layoutWidth = FILL_PARENT; + credentialsTable.addChild(passwordLine); + } + addChild(credentialsTable); + + auto onlyToAppleLabel = new TextWidget(null, "your credentials are only sent to Apple"d); + onlyToAppleLabel.alignment = Align.Center; + addChild(onlyToAppleLabel); + + auto loginBox = new HorizontalLayout(); + loginBox.layoutWidth = FILL_PARENT; + { + loginBox.addChild(new HSpacer()); + + auto button = new Button(null, "Log-in"d); + button.click = (_) { + string username = usernameLine.text().to!string(); + string password = passwordLine.text().to!string(); + + setBusy(true); + + new Thread({ + DeveloperSession session = DeveloperSession.login( + device, + adi, + username, + password, + (sendCode, submitCode) { + import slf4d; + getLogger().error("Cannot handle 2FA yet"); + auto window = window(); + window.executeInUiThread({ + TFAFrame.tfa(window.parentWindow(), sendCode, submitCode); + window.close(); + }); + // sendCode(); + // stdout.flush(); + // string code = readln(); + // submitCode(code); + }).match!( + (DeveloperSession session) => session, + (AppleLoginError error) { + errorLabel.executeInUiThread({ + errorLabel.text = format!"%s (%d)"d(error.description, error); + errorLabel.visibility = Visibility.Visible; + + setBusy(false); + }); + return null; + }); + + if (session) { + auto window = window(); + window.executeInUiThread({ + window.close(); + onCompletion(session); + }); + } + }).start(); + return true; + }; + loginBox.addChild(button); + } + addChild(loginBox); + setBusy(true); + } + + void setBusy(bool val) { + window().overrideCursorType(val ? CursorType.WaitArrow : CursorType.NotSet); + // usernameLine.enabled = val; + // passwordLine.enabled = val; + enabled = !val; + } + + static void login(Device device, ADI adi, Window parentWindow, void delegate(DeveloperSession) onCompletion) { + auto loginWindow = Platform.instance.createWindow("Log-in to Apple", parentWindow, WindowFlag.ExpandSize, 1, 1); + loginWindow.mainWidget = new LoginFrame(device, adi, onCompletion); + loginWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + loginWindow.show(); + } +} diff --git a/dlangui/ui/mainframe.d b/frontends/dlangui/source/ui/mainframe.d similarity index 96% rename from dlangui/ui/mainframe.d rename to frontends/dlangui/source/ui/mainframe.d index 0c3d1a7..3e88144 100644 --- a/dlangui/ui/mainframe.d +++ b/frontends/dlangui/source/ui/mainframe.d @@ -119,7 +119,7 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ } catch (Exception ex) { log.infoF!"Can't connect to the device: %s"(ex); } - window().invalidate(); + window().executeInUiThread(() => window().invalidate()); }).start(); return true; }; @@ -129,6 +129,7 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ actionsFrame = new FrameLayout(); actionsFrame.layoutWidth = FILL_PARENT; actionsFrame.layoutHeight = FILL_PARENT; + actionsFrame.visibility = Visibility.Invisible; { auto trustLabel = new TextWidget("TRUST", "Please unlock your device and trust the computer"d); trustLabel.alignment = Align.Center; @@ -137,7 +138,6 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ auto actions = new TabWidget("ACTIONS"); actions.layoutWidth = FILL_PARENT; actions.layoutHeight = FILL_PARENT; - actions.visibility = Visibility.Invisible; { auto deviceInfoTable = new TableLayout("INFO"); deviceInfoTable.layoutWidth = FILL_PARENT; @@ -313,6 +313,8 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ actionsFrame.visibility = Visibility.Visible; } }); + + window().executeInUiThread(() => window().invalidate()); } void updateDeviceInfo(scope LockdowndClient client) { @@ -328,6 +330,13 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ versionLine.executeInUiThread({ versionLine.text = deviceInfo["ProductVersion"].str().native().to!dstring(); + + import ui.loginframe; + import ui.tfaframe; + import server.appleaccount; + import server.developersession; + LoginFrame.login(null, null, window(), (_) {}); + // TFAFrame.tfa(window(), () => true, (_) { return AppleTFAResponse(Success()); }); }); } diff --git a/frontends/dlangui/source/ui/tfaframe.d b/frontends/dlangui/source/ui/tfaframe.d new file mode 100644 index 0000000..f556fae --- /dev/null +++ b/frontends/dlangui/source/ui/tfaframe.d @@ -0,0 +1,20 @@ +module ui.tfaframe; + +import dlangui; + +import server.appleaccount; +import server.developersession; + +class TFAFrame: VerticalLayout { + this(Send2FADelegate sendCode, Submit2FADelegate submitCode) { + addChild(); + } + + static void tfa(Window parentWindow, Send2FADelegate sendCode, Submit2FADelegate submitCode) { + sendCode(); + auto tfaWindow = Platform.instance.createWindow("Sent code to your devices", parentWindow, WindowFlag.ExpandSize, 1, 1); + tfaWindow.mainWidget = new TFAFrame(sendCode, submitCode); + tfaWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + tfaWindow.show(); + } +} diff --git a/dlangui/ui/utils.d b/frontends/dlangui/source/ui/utils.d similarity index 100% rename from dlangui/ui/utils.d rename to frontends/dlangui/source/ui/utils.d diff --git a/frontends/gtk/dub.json b/frontends/gtk/dub.json new file mode 100644 index 0000000..0065779 --- /dev/null +++ b/frontends/gtk/dub.json @@ -0,0 +1,43 @@ +{ + "name": "gtk-frontend", + "targetType": "executable", + "targetPath": "../../bin/", + "targetName": "sideloader", + + "sourcePaths": [ + "source/" + ], + + "sourcePaths-linux": [ + "../common/linux/" + ], + + "sourcePaths-windows": [ + "../common/windows/" + ], + + "dependencies": { + "sideloader": { "path": "../../" }, + "gtk_d:gtk": "~>1", + "gtk_d:adw": "~>1" + }, + + "configurations": [ + { + "name": "default", + "targetType": "executable" + }, + { + "name": "static", + "targetType": "executable", + + "dflags-ldc": [ + "--link-defaultlib-shared=false" + ], + + "dflags-gdc": [ + "-defaultlib=:libgphobos.a" + ] + } + ] +} \ No newline at end of file diff --git a/frontends/gtk/dub.selections.json b/frontends/gtk/dub.selections.json new file mode 100644 index 0000000..8132870 --- /dev/null +++ b/frontends/gtk/dub.selections.json @@ -0,0 +1,22 @@ +{ + "fileVersion": 1, + "versions": { + "automem": "0.6.9", + "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, + "botan-math": "1.0.4", + "cachetools": "0.4.1", + "dxml": "0.4.4", + "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "gtk_d": "1.0.3", + "intel-intrinsics": "1.11.15", + "memutils": "1.0.9", + "plist": "~master", + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, + "requests": "2.1.1", + "sideloader": {"path":"../../"}, + "slf4d": "2.4.3", + "test_allocator": "0.3.4", + "unit-threaded": "0.10.8" + } +} diff --git a/frontends/gtk/source/main.d b/frontends/gtk/source/main.d new file mode 100644 index 0000000..e1ec4f5 --- /dev/null +++ b/frontends/gtk/source/main.d @@ -0,0 +1,83 @@ +module main; + +import core.stdc.signal; + +import file = std.file; +import std.path; +import std.process; +import std.traits; + +import glib.MessageLog; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +import constants; +import utils; + +import ui.sideloadergtkapplication; + +int main(string[] args) { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + debug { + Level level = Levels.TRACE; + } else { + Level level = Levels.INFO; + } + + signal(SIGSEGV, cast(Parameters!signal[1]) &SIGSEGV_trace); + configureLoggingProvider(new shared DefaultProvider(true, level)); + + MessageLog.logSetHandler(null, GLogLevelFlags.LEVEL_MASK | GLogLevelFlags.FLAG_FATAL | GLogLevelFlags.FLAG_RECURSION, + (logDomainC, logLevel, messageC, userData) { + auto logger = getLogger(); + Levels level; + with (GLogLevelFlags) switch (logLevel) { + case LEVEL_DEBUG: + level = Levels.DEBUG; + break; + case LEVEL_INFO: + case LEVEL_MESSAGE: + level = Levels.INFO; + break; + case LEVEL_WARNING: + case LEVEL_CRITICAL: + level = Levels.WARN; + break; + default: + level = Levels.ERROR; + break; + } + import std.string; + logger.log(level, cast(string) messageC.fromStringz(), null, cast(string) logDomainC.fromStringz(), ""); + }, null); + + string configurationPath = environment.get("XDG_CONFIG_DIR") + .orDefault("~/.config") + .buildPath(applicationName) + .expandTilde(); + + auto log = getLogger(); + log.info(versionStr); + log.infoF!"Configuration path: %s"(configurationPath); + if (!file.exists(configurationPath)) { + file.mkdirRecurse(configurationPath); + } + + return new SideloaderGtkApplication(configurationPath).run(args); +} + +private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { + this(string file = __FILE__, size_t line = __LINE__) { + super("Segmentation fault.", file, line); + } +} + +extern(C) void SIGSEGV_trace(int) @system { + throw new SegmentationFault(); +} diff --git a/linux/gtk/ui/authentication/assistantslide.d b/frontends/gtk/source/ui/authentication/assistantslide.d similarity index 100% rename from linux/gtk/ui/authentication/assistantslide.d rename to frontends/gtk/source/ui/authentication/assistantslide.d diff --git a/linux/gtk/ui/authentication/authenticationassistant.d b/frontends/gtk/source/ui/authentication/authenticationassistant.d similarity index 100% rename from linux/gtk/ui/authentication/authenticationassistant.d rename to frontends/gtk/source/ui/authentication/authenticationassistant.d diff --git a/linux/gtk/ui/authentication/loginslide.d b/frontends/gtk/source/ui/authentication/loginslide.d similarity index 100% rename from linux/gtk/ui/authentication/loginslide.d rename to frontends/gtk/source/ui/authentication/loginslide.d diff --git a/linux/gtk/ui/authentication/tfaslide.d b/frontends/gtk/source/ui/authentication/tfaslide.d similarity index 100% rename from linux/gtk/ui/authentication/tfaslide.d rename to frontends/gtk/source/ui/authentication/tfaslide.d diff --git a/linux/gtk/ui/dependencieswindow.d b/frontends/gtk/source/ui/dependencieswindow.d similarity index 96% rename from linux/gtk/ui/dependencieswindow.d rename to frontends/gtk/source/ui/dependencieswindow.d index 192bfc9..5d5569d 100644 --- a/linux/gtk/ui/dependencieswindow.d +++ b/frontends/gtk/source/ui/dependencieswindow.d @@ -20,8 +20,8 @@ import gtk.WindowHandle; import gdk.Cursor; +import app; import constants; -import main; import ui.sideloadergtkapplication; import ui.utils; @@ -72,7 +72,7 @@ class DependenciesWindow: Window { stack.setVisibleChild(downloadProgress); Thread t = new Thread(() { - auto succeeded = frontend.downloadAndInstallDeps((progress) { + auto succeeded = downloadAndInstallDeps(app.configurationPath, (progress) { runInUIThread({ downloadProgress.setFraction(progress); downloadProgress.setText(format!"%.2f %% completed"(progress * 100)); diff --git a/linux/gtk/ui/devicewidget.d b/frontends/gtk/source/ui/devicewidget.d similarity index 100% rename from linux/gtk/ui/devicewidget.d rename to frontends/gtk/source/ui/devicewidget.d diff --git a/linux/gtk/ui/mainwindow.d b/frontends/gtk/source/ui/mainwindow.d similarity index 100% rename from linux/gtk/ui/mainwindow.d rename to frontends/gtk/source/ui/mainwindow.d diff --git a/linux/gtk/ui/manageappidwindow.d b/frontends/gtk/source/ui/manageappidwindow.d similarity index 100% rename from linux/gtk/ui/manageappidwindow.d rename to frontends/gtk/source/ui/manageappidwindow.d diff --git a/linux/gtk/ui/managecertificateswindow.d b/frontends/gtk/source/ui/managecertificateswindow.d similarity index 100% rename from linux/gtk/ui/managecertificateswindow.d rename to frontends/gtk/source/ui/managecertificateswindow.d diff --git a/linux/gtk/ui/numberentry.d b/frontends/gtk/source/ui/numberentry.d similarity index 100% rename from linux/gtk/ui/numberentry.d rename to frontends/gtk/source/ui/numberentry.d diff --git a/linux/gtk/ui/sideloadergtkapplication.d b/frontends/gtk/source/ui/sideloadergtkapplication.d similarity index 95% rename from linux/gtk/ui/sideloadergtkapplication.d rename to frontends/gtk/source/ui/sideloadergtkapplication.d index 220d12e..c88a71d 100644 --- a/linux/gtk/ui/sideloadergtkapplication.d +++ b/frontends/gtk/source/ui/sideloadergtkapplication.d @@ -24,9 +24,9 @@ import slf4d; import provision; +import app; import constants; import imobiledevice; -import main; import ui.authentication.authenticationassistant; import ui.dependencieswindow; @@ -36,15 +36,19 @@ import ui.manageappidwindow; import ui.managecertificateswindow; import ui.utils; -// TODO REMOVE THAT AND USE SOMETHING NOT TIED TO THE GTK FRONTEND +// TODO REMOVE THAT __gshared static SideloaderGtkApplication runningApplication; class SideloaderGtkApplication: Application { string configurationPath; MainWindow mainWindow; - auto device() => frontend.device; - auto adi() => frontend.adi; + + Device _device; + ADI _adi; + + auto device() => _device; + auto adi() => _adi; this(string configurationPath) { super("dev.dadoum.Sideloader", ApplicationFlags.FLAGS_NONE); @@ -131,7 +135,9 @@ class SideloaderGtkApplication: Application { } void configureMainWindow() { - frontend.initializeADI(); + auto provisioningData = initializeADI(configurationPath); + _adi = provisioningData.adi; + _device = provisioningData.device; mainWindow = new MainWindow(); addWindow(mainWindow); diff --git a/linux/gtk/ui/sideloadprogresswindow.d b/frontends/gtk/source/ui/sideloadprogresswindow.d similarity index 97% rename from linux/gtk/ui/sideloadprogresswindow.d rename to frontends/gtk/source/ui/sideloadprogresswindow.d index d05659f..f7e914b 100644 --- a/linux/gtk/ui/sideloadprogresswindow.d +++ b/frontends/gtk/source/ui/sideloadprogresswindow.d @@ -58,7 +58,7 @@ class SideloadProgressWindow: Window { new Thread({ try { - sideloadFull(device, session, iosApp, (progress, message) { + sideloadFull(app.configurationPath, device, session, iosApp, (progress, message) { runInUIThread({ if (progressWindow.anim) { progressWindow.anim.pause(); diff --git a/linux/gtk/ui/toolselectionwindow.d b/frontends/gtk/source/ui/toolselectionwindow.d similarity index 100% rename from linux/gtk/ui/toolselectionwindow.d rename to frontends/gtk/source/ui/toolselectionwindow.d diff --git a/linux/gtk/ui/utils.d b/frontends/gtk/source/ui/utils.d similarity index 100% rename from linux/gtk/ui/utils.d rename to frontends/gtk/source/ui/utils.d diff --git a/frontends/swiftui/dub.json b/frontends/swiftui/dub.json new file mode 100644 index 0000000..0e7bd90 --- /dev/null +++ b/frontends/swiftui/dub.json @@ -0,0 +1,27 @@ +{ + "name": "swiftui-frontend", + "targetType": "staticLibrary", + "targetPath": "../../bin/", + "targetName": "sideloader", + + "sourcePaths": [ + "source/" + ], + + "dependencies": { + "sideloader": { "path": "../../" } + }, + + "dflags-ldc": [ + "--link-defaultlib-shared=false" + ], + + "dflags-gdc": [ + "-defaultlib=:libgphobos.a" + ], + + "lflags-osx": [ + "-rpath", + "@loader_path/../Frameworks" + ] +} \ No newline at end of file diff --git a/frontends/swiftui/dub.selections.json b/frontends/swiftui/dub.selections.json new file mode 100644 index 0000000..2eea410 --- /dev/null +++ b/frontends/swiftui/dub.selections.json @@ -0,0 +1,21 @@ +{ + "fileVersion": 1, + "versions": { + "automem": "0.6.9", + "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, + "botan-math": "1.0.4", + "cachetools": "0.4.1", + "dxml": "0.4.4", + "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "intel-intrinsics": "1.11.15", + "memutils": "1.0.9", + "plist": "~master", + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, + "requests": "2.1.1", + "sideloader": {"path":"../../"}, + "slf4d": "2.4.3", + "test_allocator": "0.3.4", + "unit-threaded": "0.10.8" + } +} diff --git a/frontends/swiftui/source/objc/developerservices.d b/frontends/swiftui/source/objc/developerservices.d new file mode 100644 index 0000000..2dc592b --- /dev/null +++ b/frontends/swiftui/source/objc/developerservices.d @@ -0,0 +1,22 @@ +module objc.developerservices; + +import core.attribute : selector; + +extern (Objective-C) +extern class NSObject +{ + static NSObject alloc() @selector("alloc"); + NSObject init() @selector("init"); +} + +extern (Objective-C) +class Test : NSObject +{ + override static Foo alloc() @selector("alloc"); + override Foo init() @selector("init"); + + final int add5(int a) @selector("bar:") + { + return a + 5; + } +} diff --git a/linux/gtk/frontend.d b/linux/gtk/frontend.d deleted file mode 100644 index 15e7542..0000000 --- a/linux/gtk/frontend.d +++ /dev/null @@ -1,77 +0,0 @@ -module frontend; - -import core.stdc.signal; - -import file = std.file; -import std.path; -import std.process; -import std.traits; - -import glib.MessageLog; - -import slf4d; -import slf4d.default_provider; -import slf4d.provider; - -import constants; -import utils; - -import app.frontend; -import ui.sideloadergtkapplication; - -shared class GtkFrontend: Frontend { - string _configurationPath; - - this() { - MessageLog.logSetHandler(null, GLogLevelFlags.LEVEL_MASK | GLogLevelFlags.FLAG_FATAL | GLogLevelFlags.FLAG_RECURSION, - (logDomainC, logLevel, messageC, userData) { - auto logger = getLogger(); - Levels level; - with (GLogLevelFlags) switch (logLevel) { - case LEVEL_DEBUG: - level = Levels.DEBUG; - break; - case LEVEL_INFO: - case LEVEL_MESSAGE: - level = Levels.INFO; - break; - case LEVEL_WARNING: - case LEVEL_CRITICAL: - level = Levels.WARN; - break; - default: - level = Levels.ERROR; - break; - } - import std.string; - logger.log(level, cast(string) messageC.fromStringz(), null, cast(string) logDomainC.fromStringz(), ""); - }, null); - - _configurationPath = environment.get("XDG_CONFIG_DIR") - .orDefault("~/.config") - .buildPath(applicationName) - .expandTilde(); - } - - override string configurationPath() { - return _configurationPath; - } - - override int run(string[] args) { - signal(SIGSEGV, cast(Parameters!signal[1]) &SIGSEGV_trace); - return new SideloaderGtkApplication(_configurationPath).run(args); - } -} - -Frontend makeFrontend() => new GtkFrontend(); -shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared DefaultProvider(true, rootLoggingLevel); - -private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { - this(string file = __FILE__, size_t line = __LINE__) { - super("Segmentation fault.", file, line); - } -} - -extern(C) void SIGSEGV_trace(int) @system { - throw new SegmentationFault(); -} diff --git a/source/app/frontend.d b/source/app/frontend.d deleted file mode 100644 index 7a6ced8..0000000 --- a/source/app/frontend.d +++ /dev/null @@ -1,105 +0,0 @@ -module app.frontend; - -import file = std.file; -import std.format; -import std.math; -import std.path; -import std.zip; - -import slf4d; - -import requests; - -import provision; - -import constants; - -shared abstract class Frontend { - __gshared Device device; - __gshared ADI adi; - - abstract string configurationPath(); - abstract int run(string[] args); - - final void initializeADI() { - auto log = getLogger(); - device = new Device(configurationPath.buildPath("device.json")); - - if (!device.initialized) { - log.info("Creating device..."); - - import std.digest; - import std.random; - import std.range; - import std.uni; - import std.uuid; - device.serverFriendlyDescription = " "; - device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); - device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); - device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); - log.info("Device created successfully."); - } - log.debug_("Device OK."); - - adi = new ADI(configurationPath.buildPath("lib")); - adi.provisioningPath = configurationPath; - adi.identifier = device.adiIdentifier; - - if (!adi.isMachineProvisioned(-2)) { - log.info("Provisioning device..."); - - ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); - provisioningSession.provision(-2); - log.info("Device provisioned successfully."); - } - log.debug_("Provisioning OK."); - } - - final bool downloadAndInstallDeps(bool delegate(float progress) downloadCallback) { - auto log = getLogger(); - - log.info("Downloading APK..."); - Request request = Request(); - request.sslSetVerifyPeer(false); - request.useStreaming = true; - - auto response = request.get(nativesUrl); - auto responseStream = response.receiveAsRange(); - - auto size = cast(float) response.contentLength; - size = size ? size : 150_000_000.0 /+ Rough estimate if we don't know the exact size. +/; - - ubyte[] apkData; - while(!responseStream.empty) { - if (downloadCallback(cast(float) response.contentReceived / size)) - return false; - apkData ~= responseStream.front; - responseStream.popFront(); - } - downloadCallback(1.); - - auto apk = new ZipArchive(apkData); - auto dir = apk.directory(); - - string libPath = configurationPath.buildPath("lib"); - if (!file.exists(libPath)) { - file.mkdir(libPath); - } - - version (X86_64) { - enum string architectureIdentifier = "x86_64"; - } else version (X86) { - enum string architectureIdentifier = "x86"; - } else version (AArch64) { - enum string architectureIdentifier = "arm64-v8a"; - } else version (ARM) { - enum string architectureIdentifier = "armeabi-v7a"; - } else { - static assert(false, "Architecture not supported :("); - } - file.write(libPath.buildPath("libCoreADI.so"), apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libCoreADI.so"])); - file.write(libPath.buildPath("libstoreservicescore.so"), apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libstoreservicescore.so"])); - log.info("Extracted successfully!"); - return true; - } -} diff --git a/source/app/package.d b/source/app/package.d new file mode 100644 index 0000000..d27e701 --- /dev/null +++ b/source/app/package.d @@ -0,0 +1,103 @@ +module app; + +import file = std.file; +import std.math; +import std.path; +import std.zip; + +import slf4d; + +import requests; + +import provision; + +import constants; + +struct ProvisioningData { + Device device; + ADI adi; +} + +bool downloadAndInstallDeps(string configurationPath, bool delegate(float progress) downloadCallback) { + auto log = getLogger(); + + log.info("Downloading APK..."); + Request request = Request(); + request.sslSetVerifyPeer(false); + request.useStreaming = true; + + auto response = request.get(nativesUrl); + auto responseStream = response.receiveAsRange(); + + auto size = cast(float) response.contentLength; + size = size ? size : 150_000_000.0 /+ Rough estimate if we don't know the exact size. +/; + + ubyte[] apkData; + while(!responseStream.empty) { + if (downloadCallback(cast(float) response.contentReceived / size)) + return false; + apkData ~= responseStream.front; + responseStream.popFront(); + } + downloadCallback(1.); + + auto apk = new ZipArchive(apkData); + auto dir = apk.directory(); + + string libPath = configurationPath.buildPath("lib"); + if (!file.exists(libPath)) { + file.mkdir(libPath); + } + + version (X86_64) { + enum string architectureIdentifier = "x86_64"; + } else version (X86) { + enum string architectureIdentifier = "x86"; + } else version (AArch64) { + enum string architectureIdentifier = "arm64-v8a"; + } else version (ARM) { + enum string architectureIdentifier = "armeabi-v7a"; + } else { + static assert(false, "Architecture not supported :("); + } + file.write(libPath.buildPath("libCoreADI.so"), apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libCoreADI.so"])); + file.write(libPath.buildPath("libstoreservicescore.so"), apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libstoreservicescore.so"])); + log.info("Extracted successfully!"); + return true; +} + +ProvisioningData initializeADI(string configurationPath) { + auto log = getLogger(); + auto device = new Device(configurationPath.buildPath("device.json")); + + if (!device.initialized) { + log.info("Creating device..."); + + import std.digest; + import std.random; + import std.range; + import std.uni; + import std.uuid; + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); + device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); + log.info("Device created successfully."); + } + log.debug_("Device OK."); + + auto adi = new ADI(configurationPath.buildPath("lib")); + adi.provisioningPath = configurationPath; + adi.identifier = device.adiIdentifier; + + if (!adi.isMachineProvisioned(-2)) { + log.info("Provisioning device..."); + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(-2); + log.info("Device provisioned successfully."); + } + log.debug_("Provisioning OK."); + + return ProvisioningData(device, adi); +} diff --git a/source/imobiledevice/package.d b/source/imobiledevice/package.d index 5c93677..2af8f02 100644 --- a/source/imobiledevice/package.d +++ b/source/imobiledevice/package.d @@ -103,7 +103,9 @@ public class iDevice { } ~this() { - idevice_free(handle).assertSuccess(); + if (handle) { // it could have been partially initialized + idevice_free(handle).assertSuccess(); + } } } diff --git a/source/main.d b/source/main.d deleted file mode 100644 index 2ebed49..0000000 --- a/source/main.d +++ /dev/null @@ -1,52 +0,0 @@ -import std.base64; -import std.format; -import std.getopt; -import std.range; -import std.sumtype; -import std.string; - -import file = std.file; - -import slf4d; - -import constants; -import utils; - -import app.frontend; -import native_frontend = frontend; -import version_string; - -Frontend frontend; - -int main(string[] args) { - Levels logLevel = Levels.INFO; - debug { - logLevel = Levels.DEBUG; - } - - bool traceLog; - getopt( - args, - "trace", "Write more logs", &traceLog - ); - - if (traceLog) { - logLevel = Levels.TRACE; - } - - configureLoggingProvider(native_frontend.makeLoggingProvider(logLevel)); - - import core.stdc.locale; - setlocale(LC_ALL, ""); - - Logger log = getLogger(); - - frontend = native_frontend.makeFrontend(); - log.info(versionStr); - log.infoF!"Configuration path: %s"(frontend.configurationPath()); - if (!file.exists(frontend.configurationPath)) { - file.mkdirRecurse(frontend.configurationPath); - } - - return frontend.run(args); -} diff --git a/source/sideload/package.d b/source/sideload/package.d index 8fa2470..d4e2570 100644 --- a/source/sideload/package.d +++ b/source/sideload/package.d @@ -22,9 +22,8 @@ public import sideload.bundle; import sideload.certificateidentity; import sideload.sign; -import main; - void sideloadFull( + string configurationPath, iDevice device, DeveloperSession developer, Application app, @@ -54,7 +53,7 @@ void sideloadFull( // create a certificate for the developer progressCallback(3 / STEP_COUNT, "Generating a certificate for Sideloader"); - auto certIdentity = new CertificateIdentity(frontend.configurationPath, developer); + auto certIdentity = new CertificateIdentity(configurationPath, developer); // check if we registered an app id for it (if not create it) progressCallback(4 / STEP_COUNT, "Creating App IDs for the application"); diff --git a/windows/winforms/frontend.d b/windows/winforms/frontend.d deleted file mode 100644 index 92d4427..0000000 --- a/windows/winforms/frontend.d +++ /dev/null @@ -1,48 +0,0 @@ -module frontend; - -import core.sys.windows.winbase; -import core.sys.windows.windef; - -import std.path; -import std.process; - -import dfl; - -import slf4d; -import slf4d.provider; - -import constants; -import app.frontend; - -import graphical_app; -import logging; -import ui.sideloaderform; - -shared class WindowsFrontend: Frontend { - string _configurationPath; - - this() { - Application.enableVisualStyles(); - _configurationPath = environment["LocalAppData"].buildPath(applicationName); - } - - override string configurationPath() { - return _configurationPath; - } - - override int run(string[] args) { - SetUnhandledExceptionFilter(&SIGSEGV_win); - try { - Application.run(new SideloaderForm()); - return 0; - } catch (Exception ex) { - getLogger().errorF!"Unhandled exception: %s"(ex); - msgBox(ex.msg, "Unhandled exception!", MsgBoxButtons.OK, MsgBoxIcon.ERROR); - throw ex; - } - } -} - -Frontend makeFrontend() => new WindowsFrontend(); - -shared(LoggingProvider) makeLoggingProvider(Level rootLoggingLevel) => new shared OutputDebugStringLoggingProvider(rootLoggingLevel); diff --git a/windows/winforms/ui/dependenciesform.d b/windows/winforms/ui/dependenciesform.d deleted file mode 100644 index be3f3fd..0000000 --- a/windows/winforms/ui/dependenciesform.d +++ /dev/null @@ -1,103 +0,0 @@ -module ui.dependenciesform; - -import core.thread; - -import std.format; - -import slf4d; - -import dfl; - -import constants; -import main; - -class DependenciesForm: Form { - private ProgressBar downloadProgressBar; - private Button proceedButton; - private Label label1; - private __gshared bool succeeded = false; - private __gshared bool abort = false; - private Thread dlThread; - - this() { - this.downloadProgressBar = new ProgressBar(); - this.proceedButton = new Button(); - this.label1 = new Label(); - this.suspendLayout(); - // - // downloadProgressBar - // - this.downloadProgressBar.location = Point(13, 120); - this.downloadProgressBar.maximum = 1000; - this.downloadProgressBar.name = "downloadProgressBar"; - this.downloadProgressBar.size = Size(218, 29); - // this.downloadProgressBar.tabIndex = 0; - // - // proceedButton - // - this.proceedButton.click ~= &proceedButton_Clicked; - this.proceedButton.location = Point(237, 120); - this.proceedButton.name = "proceedButton"; - this.proceedButton.size = Size(85, 29); - // this.proceedButton.tabIndex = 1; - this.proceedButton.text = "Proceed"; - // this.proceedButton.useVisualStyleBackColor = true; - // - // label1 - // - this.label1.location = Point(10, 9); - this.label1.name = "label1"; - this.label1.size = Size(312, 108); - // this.label1.tabIndex = 2; - this.label1.text = applicationName ~ " requires some libraries to be installed.\nInstalling them will cause a ≈150 MB download and 5 MB of storage space.\n\nProceed?"; - // - // DependenciesForm - // - // this.autoScaleDimensions = SizeF(6F, 13F); - // this.autoScaleMode = AutoScaleMode.Font; - this.clientSize = Size(334, 161); - this.controls.add(this.label1); - this.controls.add(this.proceedButton); - this.controls.add(this.downloadProgressBar); - this.formBorderStyle = FormBorderStyle.FIXED_DIALOG; - this.name = "DependenciesForm"; - this.startPosition = FormStartPosition.CENTER_PARENT; - this.text = "Downloads required"; - this.resumeLayout(false); - - dlThread = new Thread(() { - succeeded = frontend.downloadAndInstallDeps((progress) { - auto progressPercentage = progress * 100; - this.invoke({ - downloadProgressBar.value = cast(int) (progressPercentage * 10); - downloadProgressBar.text = format!"%.2f %% completed"(progressPercentage); - }); - - return abort; - }); - - this.invoke(() => this.close()); - }); - } - - override void onClosed(EventArgs ea) { - super.onClosed(ea); - - if (succeeded) { - frontend.initializeADI(); - this.close(); - } else { - if (dlThread.isRunning() && !abort) { - abort = true; - } else { - Application.exit(); - } - } - } - - void proceedButton_Clicked(Control c, EventArgs e) { - cursor = Cursors.waitCursor(); - proceedButton.enabled = false; - dlThread.start(); - } -} diff --git a/windows/winforms/ui/sideloaderform.d b/windows/winforms/ui/sideloaderform.d deleted file mode 100644 index 79a047f..0000000 --- a/windows/winforms/ui/sideloaderform.d +++ /dev/null @@ -1,194 +0,0 @@ -module ui.sideloaderform; - -import file = std.file; -import std.format; -import std.path; -import std.process; - -import slf4d; - -import dfl; - -import constants; -import main; -import version_string; - -import ui.dependenciesform; - -class SideloaderForm: Form { - private MainMenu mainMenuStrip; - private MenuItem accountMenuItem; - private MenuItem deviceMenuItem; - private MenuItem helpMenuItem; - private MenuItem aboutSideloaderMenuItem; - private MenuItem refreshDevicesMenuItem; - private MenuItem loginMenuItem; - private MenuItem toolStripSeparator1; - private MenuItem manageAppIDsMenuItem; - private MenuItem manageCertificatesMenuItem; - private Button donateButton; - private Button installAppButton; - private ImageList deviceImageList; - private ListView deviceListView; - private Button deviceInfoButton; - - this() { - this.mainMenuStrip = new MainMenu(); - this.accountMenuItem = new MenuItem(); - this.deviceMenuItem = new MenuItem(); - this.helpMenuItem = new MenuItem(); - this.aboutSideloaderMenuItem = new MenuItem(); - this.refreshDevicesMenuItem = new MenuItem(); - this.loginMenuItem = new MenuItem(); - this.toolStripSeparator1 = new MenuItem(); - this.manageAppIDsMenuItem = new MenuItem(); - this.manageCertificatesMenuItem = new MenuItem(); - this.donateButton = new Button(); - this.installAppButton = new Button(); - this.deviceImageList = new ImageList(); - this.deviceListView = new ListView(); - this.deviceInfoButton = new Button(); - this.suspendLayout(); - // - // mainMenuStrip - // - this.mainMenuStrip.menuItems.addRange([ - this.accountMenuItem, - this.deviceMenuItem, - this.helpMenuItem - ]); - // - // accountMenuItem - // - this.accountMenuItem.menuItems.addRange([ - this.loginMenuItem, - this.toolStripSeparator1, - this.manageAppIDsMenuItem, - this.manageCertificatesMenuItem - ]); - this.accountMenuItem.text = "Account"; - // - // deviceMenuItem - // - this.deviceMenuItem.menuItems.addRange([ - this.refreshDevicesMenuItem - ]); - this.deviceMenuItem.text = "Device"; - // - // helpMenuItem - // - this.helpMenuItem.menuItems.addRange([ - this.aboutSideloaderMenuItem - ]); - this.helpMenuItem.text = "Help"; - // - // aboutSideloaderMenuItem - // - this.aboutSideloaderMenuItem.click ~= (MenuItem menuItem, EventArgs ea) { - msgBox(this, format!rawAboutText(versionStr, "DFL (D Forms Library)")); - }; - this.aboutSideloaderMenuItem.text = "About Sideloader"; - // - // refreshDevicesMenuItem - // - this.refreshDevicesMenuItem.text = "Refresh device list"; - // - // loginMenuItem - // - this.loginMenuItem.text = "Log-in"; - // - // toolStripSeparator1 - // - this.toolStripSeparator1.text = "-"; - // - // manageAppIDsMenuItem - // - this.manageAppIDsMenuItem.text = "Manage App IDs"; - this.manageAppIDsMenuItem.enabled = false; - // - // manageCertificatesMenuItem - // - this.manageCertificatesMenuItem.text = "Manage certificates"; - this.manageCertificatesMenuItem.enabled = false; - // - // donateButton - // - this.donateButton.anchor = (cast(AnchorStyles)((AnchorStyles.BOTTOM | AnchorStyles.RIGHT))); - this.donateButton.click ~= &donateButton_Clicked; - this.donateButton.location = Point(652, 438); - this.donateButton.name = "donateButton"; - this.donateButton.size = Size(140, 32); - // this.donateButton.tabIndex = 1; - this.donateButton.text = "Donate"; - // this.donateButton.useVisualStyleBackColor = true; - // - // installAppButton - // - this.installAppButton.anchor = (cast(AnchorStyles)((AnchorStyles.TOP | AnchorStyles.RIGHT))); - this.installAppButton.enabled = false; - this.installAppButton.location = Point(652, 12); - this.installAppButton.name = "installAppButton"; - this.installAppButton.size = Size(140, 32); - // this.installAppButton.tabIndex = 1; - this.installAppButton.text = "Install application"; - // this.installAppButton.useVisualStyleBackColor = true; - // - // deviceImageList - // - this.deviceListView.anchor = (cast(AnchorStyles)((((AnchorStyles.TOP | AnchorStyles.BOTTOM) - | AnchorStyles.LEFT) - | AnchorStyles.RIGHT))); - // this.deviceListView.largeImageList = deviceImageList; - // this.deviceListView.smallImageList = deviceImageList; - this.deviceListView.hideSelection = false; - this.deviceListView.location = Point(12, 12); - this.deviceListView.multiSelect = false; - this.deviceListView.name = "deviceImageList"; - this.deviceListView.size = Size(632, 458); - // this.deviceListView.tabIndex = 2; - // - // deviceInfoButton - // - this.deviceInfoButton.anchor = (cast(AnchorStyles)((AnchorStyles.TOP | AnchorStyles.RIGHT))); - this.deviceInfoButton.enabled = false; - this.deviceInfoButton.location = Point(652, 52); - this.deviceInfoButton.name = "deviceInfoButton"; - this.deviceInfoButton.size = Size(140, 32); - // this.deviceInfoButton.tabIndex = 3; - this.deviceInfoButton.text = "Device infomations"; - // this.deviceInfoButton.useVisualStyleBackColor = true; - // - // SideloaderForm - // - // this.autoScaleDimensions = SizeF(6F, 12F); - // this.autoScaleMode = AutoScaleMode.Font; - this.clientSize = Size(800, 500); - this.minimumSize = Size(400, 400); - this.controls.add(this.deviceInfoButton); - this.controls.add(this.deviceListView); - this.controls.add(this.donateButton); - this.controls.add(this.installAppButton); - // this.formBorderStyle = FormBorderStyle.FIXED_DIALOG; - this.menu = this.mainMenuStrip; - this.name = "SideloaderForm"; - this.resizeRedraw = true; - this.text = "Sideloader"; - this.resumeLayout(false); - this.performLayout(); - } - - override void onLoad(EventArgs ea) { - super.onLoad(ea); - if (!(file.exists(frontend.configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(frontend.configurationPath.buildPath("lib/libCoreADI.so")))) { - // Missing dependencies - getLogger().info("Cannot find Apple libraries. Prompting the user to download them. "); - new DependenciesForm().showDialog(this); - } else { - frontend.initializeADI(); - } - } - - void donateButton_Clicked(Control c, EventArgs e) { - browse("https://github.com/sponsors/Dadoum"); - } -} From 9577c8916999041bc0e67785dc9fc06e449782d4 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 2 Nov 2023 23:21:14 +0100 Subject: [PATCH 09/36] Make a stub swiftui frontend --- frontends/swiftui/source/objc/developerservices.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontends/swiftui/source/objc/developerservices.d b/frontends/swiftui/source/objc/developerservices.d index 2dc592b..6157b97 100644 --- a/frontends/swiftui/source/objc/developerservices.d +++ b/frontends/swiftui/source/objc/developerservices.d @@ -12,8 +12,8 @@ extern class NSObject extern (Objective-C) class Test : NSObject { - override static Foo alloc() @selector("alloc"); - override Foo init() @selector("init"); + override static Test alloc() @selector("alloc"); + override Test init() @selector("init"); final int add5(int a) @selector("bar:") { From 26b89ff0b77a77ee4275e7c72df7d03a2c346b7b Mon Sep 17 00:00:00 2001 From: Dadoum Date: Fri, 3 Nov 2023 03:40:52 +0100 Subject: [PATCH 10/36] Add a Swift test package --- .../Headers/module.modulemap | 4 ++ .../Headers/sideloader.h | 1 + .../SideloaderBackend.xcframework/Info.plist | 41 +++++++++++++++++++ .../macos-arm64/Headers | 1 + .../macos-arm64/libsideloader.dylib | 1 + .../macos-x86_64/Headers | 1 + .../macos-x86_64/libsideloader.dylib | 1 + swift/Package.swift | 16 ++++++++ swift/Sources/Sideloader.swift | 3 ++ 9 files changed, 69 insertions(+) create mode 100644 swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap create mode 100644 swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h create mode 100644 swift/Dependencies/SideloaderBackend.xcframework/Info.plist create mode 120000 swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/Headers create mode 120000 swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/libsideloader.dylib create mode 120000 swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/Headers create mode 120000 swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/libsideloader.dylib create mode 100644 swift/Package.swift create mode 100644 swift/Sources/Sideloader.swift diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap b/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap new file mode 100644 index 0000000..f58187f --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap @@ -0,0 +1,4 @@ +module SideloaderBackend { + header "sideloader.h" + export * +} diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h b/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h new file mode 100644 index 0000000..ee90f0a --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h @@ -0,0 +1 @@ +void helloD(); \ No newline at end of file diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Info.plist b/swift/Dependencies/SideloaderBackend.xcframework/Info.plist new file mode 100644 index 0000000..9bad0fc --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/Info.plist @@ -0,0 +1,41 @@ + + + + + AvailableLibraries + + + HeadersPath + Headers + LibraryIdentifier + macos-x86_64 + LibraryPath + libsideloader.dylib + SupportedArchitectures + + x86_64 + + SupportedPlatform + macos + + + HeadersPath + Headers + LibraryIdentifier + macos-arm64 + LibraryPath + libsideloader.dylib + SupportedArchitectures + + arm64 + + SupportedPlatform + macos + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/Headers b/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/Headers new file mode 120000 index 0000000..9a03d49 --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/Headers @@ -0,0 +1 @@ +../Headers/ \ No newline at end of file diff --git a/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/libsideloader.dylib b/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/libsideloader.dylib new file mode 120000 index 0000000..42f108e --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/macos-arm64/libsideloader.dylib @@ -0,0 +1 @@ +../../../../bin/libsideloader.dylib \ No newline at end of file diff --git a/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/Headers b/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/Headers new file mode 120000 index 0000000..9a03d49 --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/Headers @@ -0,0 +1 @@ +../Headers/ \ No newline at end of file diff --git a/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/libsideloader.dylib b/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/libsideloader.dylib new file mode 120000 index 0000000..42f108e --- /dev/null +++ b/swift/Dependencies/SideloaderBackend.xcframework/macos-x86_64/libsideloader.dylib @@ -0,0 +1 @@ +../../../../bin/libsideloader.dylib \ No newline at end of file diff --git a/swift/Package.swift b/swift/Package.swift new file mode 100644 index 0000000..02e1504 --- /dev/null +++ b/swift/Package.swift @@ -0,0 +1,16 @@ +// swift-tools-version: 5.8 + +import PackageDescription + +let package = Package( + name: "Sideloader", + platforms: [.macOS(.v12), .iOS(.v15)], + targets: [ + .binaryTarget(name: "SideloaderBackend", path: "Dependencies/SideloaderBackend.xcframework"), + .executableTarget( + name: "Sideloader", + dependencies: ["SideloaderBackend"], + path: "Sources" + ), + ] +) diff --git a/swift/Sources/Sideloader.swift b/swift/Sources/Sideloader.swift new file mode 100644 index 0000000..705896c --- /dev/null +++ b/swift/Sources/Sideloader.swift @@ -0,0 +1,3 @@ +import SideloaderBackend + +helloD(); From aa5a9981790a401334e51ef55b97d5d052b41533 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sat, 4 Nov 2023 04:08:44 +0100 Subject: [PATCH 11/36] Add basic Swift bindings generator, for the SwiftUI frontend --- .github/workflows/build-cli.yml | 20 +----- .github/workflows/build-dlangui.yml | 20 +----- .github/workflows/build-gtk.yml | 22 +----- .gitignore | 2 + dub.json | 2 +- dub.selections.json | 2 +- frontends/dlangui/source/main.d | 29 ++++++-- .../dlangui/source/ui/dependenciesframe.d | 27 ++++++++ frontends/dlangui/source/ui/loginframe.d | 17 +++-- frontends/dlangui/source/ui/mainframe.d | 12 ++-- frontends/dlangui/source/ui/tfaframe.d | 4 +- frontends/swiftui/dub.json | 6 +- frontends/swiftui/source/cpp_helpers.d | 69 +++++++++++++++++++ .../swiftui/source/objc/developerservices.d | 22 ------ frontends/swiftui/source/test1.d | 9 +++ .../Headers/module.modulemap | 1 + .../Headers/sideloader.h | 4 +- swift/Package.swift | 3 +- swift/Sources/Sideloader.swift | 13 +++- 19 files changed, 177 insertions(+), 107 deletions(-) create mode 100644 frontends/dlangui/source/ui/dependenciesframe.d create mode 100644 frontends/swiftui/source/cpp_helpers.d delete mode 100644 frontends/swiftui/source/objc/developerservices.d create mode 100644 frontends/swiftui/source/test1.d diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 4e6aafd..4c10ab7 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -53,9 +53,7 @@ jobs: compiler: ldc-1.33.0 - name: Install dependencies - run: | - sudo dpkg --add-architecture i386 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d @@ -110,21 +108,7 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: | - sudo rm -Rf /etc/apt/sources.list.d - cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse - deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner - EOF - sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + run: sudo apt-get update && sudo apt-get install -y elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index 5ceb23c..e01e4be 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -55,9 +55,7 @@ jobs: compiler: ldc-1.33.0 - name: Install dependencies - run: | - sudo dpkg --add-architecture i386 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d @@ -114,21 +112,7 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: | - sudo rm -Rf /etc/apt/sources.list.d - cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse - deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner - EOF - sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + run: sudo apt-get update && sudo apt-get install -y elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index ac64070..7b65b75 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -41,8 +41,6 @@ jobs: ${{github.workspace}}/bin/sideloader-gtk-linux-x86_64.dbg build-i686: - # Does not work yet - if: false runs-on: ubuntu-22.04 steps: @@ -55,9 +53,7 @@ jobs: compiler: ldc-1.33.0 - name: Install dependencies - run: | - sudo dpkg --add-architecture i386 - sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils gcc-i686-linux-gnu libc6-dev - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d @@ -112,21 +108,7 @@ jobs: tar -xf ./ldc2-1.32.2-linux-aarch64.tar.xz -C $HOME - name: Install dependencies - run: | - sudo rm -Rf /etc/apt/sources.list.d - cat << EOF | sudo tee /etc/apt/sources.list - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-updates main jammy - deb [arch=amd64,i386] http://us.archive.ubuntu.com/ubuntu jammy-backports main jammy - - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse - deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse - deb [arch=arm64,armhf] http://archive.canonical.com/ubuntu jammy partner - EOF - sudo dpkg --add-architecture arm64 - sudo apt-get update && sudo apt-get install -y libc6-arm64-cross libz-dev:arm64 elfutils gcc-aarch64-linux-gnu + run: sudo apt-get update && sudo apt-get install -y elfutils gcc-aarch64-linux-gnu - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d diff --git a/.gitignore b/.gitignore index 83058d1..717cb3e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ docs/ # VSCode .vscode/ +/swift/.build/ +/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader-autogen.h diff --git a/dub.json b/dub.json index 99f3e7b..150e86a 100644 --- a/dub.json +++ b/dub.json @@ -5,7 +5,7 @@ "Dadoum" ], - "targetType": "sourceLibrary", + "targetType": "staticLibrary", "stringImportPaths": ["resources/"], "buildRequirements": ["allowWarnings", "requireBoundsCheck"], diff --git a/dub.selections.json b/dub.selections.json index 5bfad49..471289e 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -27,7 +27,7 @@ "memutils": "1.0.9", "mir-core": "1.6.0", "plist": "~master", - "plist-d": {"version":"5020d8e45ca2c77183a44ce04053ccbf8bc83262","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, "requests": "2.1.1", "silly": "1.2.0-dev.2", diff --git a/frontends/dlangui/source/main.d b/frontends/dlangui/source/main.d index 77a086f..735a189 100644 --- a/frontends/dlangui/source/main.d +++ b/frontends/dlangui/source/main.d @@ -1,6 +1,7 @@ module main; import core.runtime; +import core.stdc.signal; import file = std.file; import std.path; @@ -19,6 +20,7 @@ import constants; import utils; import ui.mainframe; +import ui.dependenciesframe; mixin APP_ENTRY_POINT; @@ -38,6 +40,7 @@ extern (C) int UIAppMain() { toolList(device); }).start(); + signal(SIGSEGV, cast(Parameters!signal[1]) &SIGSEGV_trace); version(Windows) { import graphical_app; SetUnhandledExceptionFilter(&SIGSEGV_win); @@ -59,18 +62,32 @@ extern (C) int UIAppMain() { } configurationPath = configurationPath.buildPath(applicationName); + Log.setStdoutLogger(); + // Most of the time on GNOME, SDL is wrong about DPI. So we just override it. if (environment.get("XDG_CURRENT_DESKTOP") == "GNOME" && environment.get("XDG_SESSION_TYPE") == "wayland") { overrideScreenDPI(96); } - Log.setStdoutLogger(); - getLogger().info("Using DlangUI frontend."); - Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 350, 400); - w.mainWidget = new MainFrame(); - w.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; - w.show(); + + DependenciesFrame.ensureDeps(configurationPath, { + Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 350, 400); + w.mainWidget = new MainFrame(); + w.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + w.show(); + }); return Platform.instance.enterMessageLoop(); } + +private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { + this(string file = __FILE__, size_t line = __LINE__) { + super("Segmentation fault.", file, line); + } +} + +extern(C) void SIGSEGV_trace(int) @system { + throw new SegmentationFault(); +} + diff --git a/frontends/dlangui/source/ui/dependenciesframe.d b/frontends/dlangui/source/ui/dependenciesframe.d new file mode 100644 index 0000000..90ca27e --- /dev/null +++ b/frontends/dlangui/source/ui/dependenciesframe.d @@ -0,0 +1,27 @@ +module ui.dependenciesframe; + +import file = std.file; +import std.path; + +import slf4d; + +import dlangui; + +class DependenciesFrame: VerticalLayout { + this() { + addChild(new TextWidget(null, "Deps needed plz download"d)); + addChild(new Button(null, "Yes plz"d)); + } + + static void ensureDeps(string configurationPath, void delegate() onCompletion) { + if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { + // Missing dependencies + auto tfaWindow = Platform.instance.createWindow("Download required.", null, WindowFlag.ExpandSize, 1, 1); + tfaWindow.mainWidget = new DependenciesFrame(); + tfaWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + tfaWindow.show(); + } else { + onCompletion(); + } + } +} diff --git a/frontends/dlangui/source/ui/loginframe.d b/frontends/dlangui/source/ui/loginframe.d index 50ebbc3..51998d3 100644 --- a/frontends/dlangui/source/ui/loginframe.d +++ b/frontends/dlangui/source/ui/loginframe.d @@ -19,6 +19,8 @@ class LoginFrame: VerticalLayout { EditLine usernameLine; EditLine passwordLine; + Button button; + this(Device device, ADI adi, void delegate(DeveloperSession) onCompletion) { auto errorLabel = new TextWidget("LOGIN_ERROR", ""d); errorLabel.alignment = Align.Center; @@ -56,7 +58,7 @@ class LoginFrame: VerticalLayout { { loginBox.addChild(new HSpacer()); - auto button = new Button(null, "Log-in"d); + button = new Button(null, "Log-in"d); button.click = (_) { string username = usernameLine.text().to!string(); string password = passwordLine.text().to!string(); @@ -77,10 +79,6 @@ class LoginFrame: VerticalLayout { TFAFrame.tfa(window.parentWindow(), sendCode, submitCode); window.close(); }); - // sendCode(); - // stdout.flush(); - // string code = readln(); - // submitCode(code); }).match!( (DeveloperSession session) => session, (AppleLoginError error) { @@ -106,20 +104,21 @@ class LoginFrame: VerticalLayout { loginBox.addChild(button); } addChild(loginBox); - setBusy(true); } void setBusy(bool val) { window().overrideCursorType(val ? CursorType.WaitArrow : CursorType.NotSet); - // usernameLine.enabled = val; - // passwordLine.enabled = val; + usernameLine.enabled = !val; + passwordLine.enabled = !val; + button.enabled = !val; enabled = !val; } static void login(Device device, ADI adi, Window parentWindow, void delegate(DeveloperSession) onCompletion) { auto loginWindow = Platform.instance.createWindow("Log-in to Apple", parentWindow, WindowFlag.ExpandSize, 1, 1); - loginWindow.mainWidget = new LoginFrame(device, adi, onCompletion); + auto frame = new LoginFrame(device, adi, onCompletion);loginWindow.mainWidget = frame; loginWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; loginWindow.show(); + frame.setBusy(false); } } diff --git a/frontends/dlangui/source/ui/mainframe.d b/frontends/dlangui/source/ui/mainframe.d index 3e88144..c5db7ac 100644 --- a/frontends/dlangui/source/ui/mainframe.d +++ b/frontends/dlangui/source/ui/mainframe.d @@ -21,6 +21,8 @@ import constants; import sideload; import tools; +import ui.loginframe; +import ui.tfaframe; import ui.utils; class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ { @@ -254,6 +256,11 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ installButton.layoutWidth = FILL_PARENT; installButton.layoutHeight = WRAP_CONTENT; installButton.enabled = false; + installButton.click = (_) { + // TODO + // LoginFrame.login(null, null, window(), (_) {}); + return true; + }; installFrame.addChild(installButton); auto installProgressBar = new ProgressBarWidget(); @@ -331,11 +338,6 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ versionLine.executeInUiThread({ versionLine.text = deviceInfo["ProductVersion"].str().native().to!dstring(); - import ui.loginframe; - import ui.tfaframe; - import server.appleaccount; - import server.developersession; - LoginFrame.login(null, null, window(), (_) {}); // TFAFrame.tfa(window(), () => true, (_) { return AppleTFAResponse(Success()); }); }); } diff --git a/frontends/dlangui/source/ui/tfaframe.d b/frontends/dlangui/source/ui/tfaframe.d index f556fae..9ac17f9 100644 --- a/frontends/dlangui/source/ui/tfaframe.d +++ b/frontends/dlangui/source/ui/tfaframe.d @@ -7,12 +7,12 @@ import server.developersession; class TFAFrame: VerticalLayout { this(Send2FADelegate sendCode, Submit2FADelegate submitCode) { - addChild(); + // addChild(); } static void tfa(Window parentWindow, Send2FADelegate sendCode, Submit2FADelegate submitCode) { sendCode(); - auto tfaWindow = Platform.instance.createWindow("Sent code to your devices", parentWindow, WindowFlag.ExpandSize, 1, 1); + auto tfaWindow = Platform.instance.createWindow("A code has been sent", parentWindow, WindowFlag.ExpandSize, 1, 1); tfaWindow.mainWidget = new TFAFrame(sendCode, submitCode); tfaWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; tfaWindow.show(); diff --git a/frontends/swiftui/dub.json b/frontends/swiftui/dub.json index 0e7bd90..189f94c 100644 --- a/frontends/swiftui/dub.json +++ b/frontends/swiftui/dub.json @@ -1,6 +1,6 @@ { "name": "swiftui-frontend", - "targetType": "staticLibrary", + "targetType": "dynamicLibrary", "targetPath": "../../bin/", "targetName": "sideloader", @@ -13,7 +13,9 @@ }, "dflags-ldc": [ - "--link-defaultlib-shared=false" + "--link-defaultlib-shared=false", + "-HC", + "--HCf=../../swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader-autogen.h" ], "dflags-gdc": [ diff --git a/frontends/swiftui/source/cpp_helpers.d b/frontends/swiftui/source/cpp_helpers.d new file mode 100644 index 0000000..2446587 --- /dev/null +++ b/frontends/swiftui/source/cpp_helpers.d @@ -0,0 +1,69 @@ +module cpp_helpers; + +template isTranslatable(T...) { + static if (T.length) { + static if (is(T[0] == U[], U)) { + enum isTranslatable = false; + } else { + enum isTranslatable = isTranslatable!(T[1..$]); + } + } else { + enum isTranslatable = true; + } +} + +mixin template BindCtor(alias U) { + import std.traits; + + alias Params = Parameters!U; + static if (isTranslatable!Params) { + this(Params params) { + handle = new T(params); + } + } +} + +mixin template BindDtor(alias U) { + import std.traits; + + ~this() { + handle.__dtor(); + } +} + +mixin template BindFunction(alias U) { + import std.traits; + + alias Params = Parameters!U; + static if (isTranslatable!Params && isTranslatable!(ReturnType!U)) { + static if (__traits(isStaticFunction, U)) { + mixin(`static auto `~__traits(identifier, U)~`(Params params) { + return `~__traits(parent, U).stringof~`.`~__traits(identifier, U)~`(params); + }`); + } else { + mixin(`auto `~__traits(identifier, U)~`(Params params) { + return handle.`~__traits(identifier, U)~`(params); + }`); + } + } +} + +mixin template WrapClass(T) { + mixin(` + extern (C++) class `~T.stringof~` { + T handle; + + static foreach (func; __traits(allMembers, T)) { + static foreach (overload; __traits(getOverloads, T, func)) { + static if (__traits(identifier, overload) == "__ctor") { + mixin BindCtor!(overload); + } else static if (__traits(identifier, overload) == "__dtor") { + mixin BindDtor!(overload); + } + mixin BindFunction!(overload); + } + } + } + + `); +} diff --git a/frontends/swiftui/source/objc/developerservices.d b/frontends/swiftui/source/objc/developerservices.d deleted file mode 100644 index 6157b97..0000000 --- a/frontends/swiftui/source/objc/developerservices.d +++ /dev/null @@ -1,22 +0,0 @@ -module objc.developerservices; - -import core.attribute : selector; - -extern (Objective-C) -extern class NSObject -{ - static NSObject alloc() @selector("alloc"); - NSObject init() @selector("init"); -} - -extern (Objective-C) -class Test : NSObject -{ - override static Test alloc() @selector("alloc"); - override Test init() @selector("init"); - - final int add5(int a) @selector("bar:") - { - return a + 5; - } -} diff --git a/frontends/swiftui/source/test1.d b/frontends/swiftui/source/test1.d new file mode 100644 index 0000000..f63cd43 --- /dev/null +++ b/frontends/swiftui/source/test1.d @@ -0,0 +1,9 @@ +module test1; + +import cpp_helpers; +import provision; + +extern(C) void hello(string a) { + import std.stdio; + writeln(a); +} diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap b/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap index f58187f..1cdbb83 100644 --- a/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap +++ b/swift/Dependencies/SideloaderBackend.xcframework/Headers/module.modulemap @@ -1,4 +1,5 @@ module SideloaderBackend { header "sideloader.h" + requires cplusplus export * } diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h b/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h index ee90f0a..de181f3 100644 --- a/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h +++ b/swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader.h @@ -1 +1,3 @@ -void helloD(); \ No newline at end of file +#include "sideloader-autogen.h" + +using DString = _d_dynamicArray; diff --git a/swift/Package.swift b/swift/Package.swift index 02e1504..9192834 100644 --- a/swift/Package.swift +++ b/swift/Package.swift @@ -10,7 +10,8 @@ let package = Package( .executableTarget( name: "Sideloader", dependencies: ["SideloaderBackend"], - path: "Sources" + path: "Sources", + swiftSettings: [.unsafeFlags(["-cxx-interoperability-mode=default"])] ), ] ) diff --git a/swift/Sources/Sideloader.swift b/swift/Sources/Sideloader.swift index 705896c..9a2bfed 100644 --- a/swift/Sources/Sideloader.swift +++ b/swift/Sources/Sideloader.swift @@ -1,3 +1,14 @@ +import Foundation import SideloaderBackend -helloD(); +extension String { + func toDString() -> DString { + let data = self.data(using: .utf8)! + return try! data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + let charBytes = bytes.bindMemory(to: CChar.self) + return DString(charBytes.count, charBytes.baseAddress) + } + } +} + +hello("Hello world from Swift".toDString()) From 1efb929023e526ff52bf8731f0494dc6a41c4fe4 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Tue, 7 Nov 2023 14:47:13 +0100 Subject: [PATCH 12/36] Fix Swift ARC with D Strings --- .../SideloaderBackend.xcframework/Info.plist | 14 ++++++++++++++ swift/Dependencies/libsideloader.so | 1 + swift/Package.swift | 9 +++++++-- swift/Sources/Sideloader.swift | 13 ++++++++----- 4 files changed, 30 insertions(+), 7 deletions(-) create mode 120000 swift/Dependencies/libsideloader.so diff --git a/swift/Dependencies/SideloaderBackend.xcframework/Info.plist b/swift/Dependencies/SideloaderBackend.xcframework/Info.plist index 9bad0fc..8624542 100644 --- a/swift/Dependencies/SideloaderBackend.xcframework/Info.plist +++ b/swift/Dependencies/SideloaderBackend.xcframework/Info.plist @@ -32,6 +32,20 @@ SupportedPlatform macos + + HeadersPath + Headers + LibraryIdentifier + linux-x86_64 + LibraryPath + libsideloader.dylib + SupportedArchitectures + + x86_64 + + SupportedPlatform + linux + CFBundlePackageType XFWK diff --git a/swift/Dependencies/libsideloader.so b/swift/Dependencies/libsideloader.so new file mode 120000 index 0000000..72bc3dd --- /dev/null +++ b/swift/Dependencies/libsideloader.so @@ -0,0 +1 @@ +../../bin/libsideloader.so \ No newline at end of file diff --git a/swift/Package.swift b/swift/Package.swift index 9192834..c2c1571 100644 --- a/swift/Package.swift +++ b/swift/Package.swift @@ -6,12 +6,17 @@ let package = Package( name: "Sideloader", platforms: [.macOS(.v12), .iOS(.v15)], targets: [ + // .systemLibrary( + // name: "SideloaderBackend", + // path: "Dependencies/SideloaderBackend.xcframework/Headers" + // ), .binaryTarget(name: "SideloaderBackend", path: "Dependencies/SideloaderBackend.xcframework"), .executableTarget( name: "Sideloader", dependencies: ["SideloaderBackend"], path: "Sources", - swiftSettings: [.unsafeFlags(["-cxx-interoperability-mode=default"])] - ), + swiftSettings: [.unsafeFlags(["-cxx-interoperability-mode=default"])], + linkerSettings: [.unsafeFlags(["-LDependencies/", "-lsideloader"])] + ) ] ) diff --git a/swift/Sources/Sideloader.swift b/swift/Sources/Sideloader.swift index 9a2bfed..a262075 100644 --- a/swift/Sources/Sideloader.swift +++ b/swift/Sources/Sideloader.swift @@ -2,13 +2,16 @@ import Foundation import SideloaderBackend extension String { - func toDString() -> DString { - let data = self.data(using: .utf8)! - return try! data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + func withDString(function: (DString) throws -> Result) rethrows -> Result { + let data = self.data(using: .ascii)! + return try! data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in let charBytes = bytes.bindMemory(to: CChar.self) - return DString(charBytes.count, charBytes.baseAddress) + let dstr = DString(charBytes.count, charBytes.baseAddress) + return try function(dstr) } } } -hello("Hello world from Swift".toDString()) +"Hello world from Swift".withDString { + hello($0) +} From ce47ba6991d56c575cecdf7108c8c95d1773290c Mon Sep 17 00:00:00 2001 From: Dadoum Date: Tue, 7 Nov 2023 14:54:33 +0100 Subject: [PATCH 13/36] Fix Swift ARC with D Strings --- frontends/swiftui/dub.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontends/swiftui/dub.json b/frontends/swiftui/dub.json index 189f94c..d11c485 100644 --- a/frontends/swiftui/dub.json +++ b/frontends/swiftui/dub.json @@ -12,7 +12,13 @@ "sideloader": { "path": "../../" } }, + "dflags-dmd": [ + "-HC", + "-HCf=../../swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader-autogen.h" + ], + "dflags-ldc": [ + "-Xcc=-mmacosx-version-min=10.14", "--link-defaultlib-shared=false", "-HC", "--HCf=../../swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader-autogen.h" From 01b82dbb1c66b5ea457395f76e3ee169578f1b14 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 14 Dec 2023 18:19:32 +0100 Subject: [PATCH 14/36] [WIP] CLI and OpenSSL update --- .github/workflows/build-cli.yml | 6 +- .github/workflows/build-dlangui.yml | 4 +- .github/workflows/build-gtk.yml | 6 +- .gitignore | 4 + README.md | 20 + dub.json | 5 +- dub.selections.json | 4 +- frontends/cli/dub.json | 6 +- frontends/cli/dub.selections.json | 7 +- frontends/cli/source/app_id.d | 254 ++++++ frontends/cli/source/certificate.d | 190 ++++ frontends/cli/source/cli_frontend.d | 218 +++++ frontends/cli/source/install.d | 63 ++ frontends/cli/source/main.d | 109 --- frontends/cli/source/sign.d | 74 ++ frontends/cli/source/team.d | 54 ++ frontends/cli/source/tool.d | 112 +++ frontends/common/linux/secret/BackendIF.d | 87 -- frontends/common/linux/secret/RetrievableIF.d | 144 --- frontends/common/linux/secret/Schema.d | 190 ---- .../common/linux/secret/SchemaAttribute.d | 83 -- frontends/common/linux/secret/Value.d | 219 ----- frontends/common/linux/secretapi.d | 2 - frontends/common/windows/graphical_app.d | 8 +- frontends/dlangui/source/main.d | 2 +- .../dlangui/source/ui/dependenciesframe.d | 46 +- frontends/dlangui/source/ui/mainframe.d | 2 +- .../gtk/source/ui/authentication/loginslide.d | 17 +- .../gtk/source/ui/managecertificateswindow.d | 32 + keyring/.gitignore | 16 + keyring/dub.json | 56 ++ keyring/dub.selections.json | 8 + keyring/libsecret/APILookup.txt | 38 + keyring/libsecret/secret/Attributes.d | 39 + keyring/libsecret/secret/BackendIF.d | 87 ++ .../libsecret}/secret/BackendT.d | 14 +- .../libsecret}/secret/Collection.d | 189 ++-- .../linux => keyring/libsecret}/secret/Item.d | 201 +++-- keyring/libsecret/secret/Password.d | 598 +++++++++++++ .../libsecret}/secret/Prompt.d | 188 ++-- keyring/libsecret/secret/RetrievableIF.d | 144 +++ .../libsecret}/secret/RetrievableT.d | 190 ++-- keyring/libsecret/secret/Schema.d | 215 +++++ keyring/libsecret/secret/SchemaAttribute.d | 83 ++ .../libsecret}/secret/Service.d | 213 +++-- keyring/libsecret/secret/Value.d | 219 +++++ .../libsecret}/secret/c/functions.d | 379 +++++--- .../libsecret}/secret/c/types.d | 256 +++--- keyring/source/keyring.d | 50 ++ keyring/source/libsecretkeyring.d | 61 ++ keyring/source/memorykeyring.d | 23 + keyring/source/osxkeyring.d | 21 + .../win32/security/credentials/package.d | 818 ++++++++++++++++++ keyring/source/windowskeyring.d | 74 ++ screenshots/screenshot-gtk-2023-11-28.png | Bin 0 -> 13326 bytes source/server/appleaccount.d | 164 ++-- source/server/developersession.d | 58 +- source/sideload/certificateidentity.d | 53 +- source/utils.d | 38 +- 59 files changed, 4739 insertions(+), 1722 deletions(-) create mode 100644 frontends/cli/source/app_id.d create mode 100644 frontends/cli/source/certificate.d create mode 100644 frontends/cli/source/cli_frontend.d create mode 100644 frontends/cli/source/install.d delete mode 100644 frontends/cli/source/main.d create mode 100644 frontends/cli/source/sign.d create mode 100644 frontends/cli/source/team.d create mode 100644 frontends/cli/source/tool.d delete mode 100644 frontends/common/linux/secret/BackendIF.d delete mode 100644 frontends/common/linux/secret/RetrievableIF.d delete mode 100644 frontends/common/linux/secret/Schema.d delete mode 100644 frontends/common/linux/secret/SchemaAttribute.d delete mode 100644 frontends/common/linux/secret/Value.d delete mode 100644 frontends/common/linux/secretapi.d create mode 100644 keyring/.gitignore create mode 100644 keyring/dub.json create mode 100644 keyring/dub.selections.json create mode 100644 keyring/libsecret/APILookup.txt create mode 100644 keyring/libsecret/secret/Attributes.d create mode 100644 keyring/libsecret/secret/BackendIF.d rename {frontends/common/linux => keyring/libsecret}/secret/BackendT.d (65%) rename {frontends/common/linux => keyring/libsecret}/secret/Collection.d (89%) rename {frontends/common/linux => keyring/libsecret}/secret/Item.d (88%) create mode 100644 keyring/libsecret/secret/Password.d rename {frontends/common/linux => keyring/libsecret}/secret/Prompt.d (58%) create mode 100644 keyring/libsecret/secret/RetrievableIF.d rename {frontends/common/linux => keyring/libsecret}/secret/RetrievableT.d (51%) create mode 100644 keyring/libsecret/secret/Schema.d create mode 100644 keyring/libsecret/secret/SchemaAttribute.d rename {frontends/common/linux => keyring/libsecret}/secret/Service.d (96%) create mode 100644 keyring/libsecret/secret/Value.d rename {frontends/common/linux => keyring/libsecret}/secret/c/functions.d (68%) rename {frontends/common/linux => keyring/libsecret}/secret/c/types.d (80%) create mode 100644 keyring/source/keyring.d create mode 100644 keyring/source/libsecretkeyring.d create mode 100644 keyring/source/memorykeyring.d create mode 100644 keyring/source/osxkeyring.d create mode 100644 keyring/source/windows/win32/security/credentials/package.d create mode 100644 keyring/source/windowskeyring.d create mode 100644 screenshots/screenshot-gtk-2023-11-28.png diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 4c10ab7..56062ae 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -7,7 +7,7 @@ env: jobs: build-x86_64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -41,6 +41,8 @@ jobs: ${{github.workspace}}/bin/sideloader-cli-linux-x86_64.dbg build-i686: + # Does not work yet + if: false runs-on: ubuntu-22.04 steps: @@ -75,7 +77,7 @@ jobs: ${{github.workspace}}/bin/sideloader-cli-linux-i686.dbg build-aarch64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/build-dlangui.yml b/.github/workflows/build-dlangui.yml index e01e4be..de20f24 100644 --- a/.github/workflows/build-dlangui.yml +++ b/.github/workflows/build-dlangui.yml @@ -7,7 +7,7 @@ env: jobs: build-x86_64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -79,7 +79,7 @@ jobs: build-aarch64: # Does not work yet if: false - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 7b65b75..080e39f 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -7,7 +7,7 @@ env: jobs: build-x86_64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -41,6 +41,8 @@ jobs: ${{github.workspace}}/bin/sideloader-gtk-linux-x86_64.dbg build-i686: + # Does not work yet + if: false runs-on: ubuntu-22.04 steps: @@ -75,7 +77,7 @@ jobs: ${{github.workspace}}/bin/sideloader-gtk-linux-i686.dbg build-aarch64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 717cb3e..477feff 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ docs/ *.o *.obj *.lst +*.a +*.lib # Idea .idea/ @@ -16,5 +18,7 @@ docs/ # VSCode .vscode/ + +# Swift /swift/.build/ /swift/Dependencies/SideloaderBackend.xcframework/Headers/sideloader-autogen.h diff --git a/README.md b/README.md index 7f3208c..b1daafe 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,26 @@ I am here to help!
Leave a star and a small tip if you feel like it! — more information at the end!
+## Usage + +### GTK + +![](screenshots/screenshot-gtk-2023-11-28.png) + +### CLI + +```sh +$ sideloader -h +Available commands: + install Install an application on the device (renames the app, register the + identifier, sign and install automatically). + sign Sign an application bundle (you need to have the App ID registered + though). + + + +``` + ## How to install Currently, the only builds available can be downloaded through GitHub Actions. diff --git a/dub.json b/dub.json index 150e86a..df0ff40 100644 --- a/dub.json +++ b/dub.json @@ -11,10 +11,7 @@ "buildRequirements": ["allowWarnings", "requireBoundsCheck"], "dependencies": { - "botan": { - "repository": "git+https://github.com/Dadoum/botan.git", - "version": "a0c206639debc7e5726c02fc399267e6a33571a0" - }, + "botan": "~>1.13", "dynamic-loader": { "repository": "git+https://github.com/Dadoum/dynamicloader.git", "version": "65a8b8b8a6d44d47e63bddc985268592ecf47764" diff --git a/dub.selections.json b/dub.selections.json index 471289e..63b10ac 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -7,7 +7,7 @@ "bindbc-loader": "1.0.3", "bindbc-opengl": "1.0.5", "bindbc-sdl": "1.0.1", - "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, + "botan": "1.13.6", "botan-math": "1.0.4", "cachetools": "0.4.1", "concepts": "0.0.9", @@ -24,6 +24,7 @@ "inilike": "1.2.2", "intel-intrinsics": "1.11.15", "isfreedesktop": "0.1.1", + "keyring": {"path":"keyring/"}, "memutils": "1.0.9", "mir-core": "1.6.0", "plist": "~master", @@ -35,6 +36,7 @@ "test_allocator": "0.3.4", "undead": "1.1.8", "unit-threaded": "0.10.8", + "windows-headers": "1.0.5", "x11": "1.0.21", "xdgpaths": "0.2.5" } diff --git a/frontends/cli/dub.json b/frontends/cli/dub.json index b3e95e6..0e14dca 100644 --- a/frontends/cli/dub.json +++ b/frontends/cli/dub.json @@ -9,7 +9,11 @@ ], "dependencies": { - "sideloader": { "path": "../../" } + "sideloader": { "path": "../../" }, + "jcli": "~>0.24.0", + "keyring": { "path": "../../keyring" }, + "progress": "~>5.0.2", + "slf4d": "~>2" }, "dflags-ldc": [ "--link-defaultlib-shared=false" diff --git a/frontends/cli/dub.selections.json b/frontends/cli/dub.selections.json index 2eea410..33047f0 100644 --- a/frontends/cli/dub.selections.json +++ b/frontends/cli/dub.selections.json @@ -7,15 +7,20 @@ "cachetools": "0.4.1", "dxml": "0.4.4", "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "gtk_d": "1.0.3", "intel-intrinsics": "1.11.15", + "jcli": "0.24.0", + "keyring": {"path":"../../keyring"}, "memutils": "1.0.9", "plist": "~master", "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "progress": "5.0.2", "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, "requests": "2.1.1", "sideloader": {"path":"../../"}, "slf4d": "2.4.3", "test_allocator": "0.3.4", - "unit-threaded": "0.10.8" + "unit-threaded": "0.10.8", + "windows-headers": "1.0.5" } } diff --git a/frontends/cli/source/app_id.d b/frontends/cli/source/app_id.d new file mode 100644 index 0000000..61c41e7 --- /dev/null +++ b/frontends/cli/source/app_id.d @@ -0,0 +1,254 @@ +module app_id; + +import std.algorithm; +import std.array; +import std.exception; +import file = std.file; +import std.stdio; +import std.typecons; + +import slf4d; +import slf4d.default_provider; + +import jcli; + +import server.developersession; + +import cli_frontend; + +// @Command("app-id", "Manage App IDs.") + +@Command("app-id list", "List App IDs.") +struct ListAppIds +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + auto appIds = appleAccount.listAppIds!iOS(team).unwrap(); + + writefln!"You have %d App IDs available out of the %d you have at your disposal."(appIds.availableQuantity, appIds.maxQuantity); + writeln("Currently registered App IDs:"); + foreach (appId; appIds.appIds) { + writefln!" - `%s` for the app `%s`, expiring on %s."(appId.identifier, appId.name, appId.expirationDate); + } + + return 0; + } +} + +@Command("app-id add", "Add a new App ID.") +struct AddAppId +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + @ArgPositional("app name") + string name; + + @ArgPositional("app identifier") + string identifier; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + appleAccount.addAppId!iOS(team, identifier, name).unwrap(); + + log.info("Done."); + + return 0; + } +} + +@Command("app-id delete", "Delete an App ID (it won't let you create more App IDs though).") +struct DeleteAppId +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + @ArgPositional("app identifier") + string identifier; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + auto appIds = appleAccount.listAppIds!iOS(team).unwrap().appIds; + auto matchingAppIds = appIds.filter!((appId) => appId.identifier == identifier).array(); + + if (matchingAppIds.length == 0) { + log.error("No matching App ID found."); + return 1; + } + + enforce(matchingAppIds.length == 1, "Multiple App ID matched?? To prevent any issue, ignoring the request."); + appleAccount.deleteAppId!iOS(team, matchingAppIds[0]).unwrap(); + + log.info("Done."); + + return 0; + } +} + +@Command("app-id download", "Download the provisioning profile for an App ID") +struct DownloadProvision +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + @ArgNamed("output|o", "Output file") + string outputPath; + + @ArgPositional("app identifier") + string identifier; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found"); + + auto team = teams[0]; + + auto appIds = appleAccount.listAppIds!iOS(team).unwrap().appIds; + auto matchingAppIds = appIds.filter!((appId) => appId.identifier == identifier).array(); + + if (matchingAppIds.length == 0) { + log.error("No matching App ID found."); + return 1; + } + + enforce(matchingAppIds.length == 1, "Multiple App ID matched?? To prevent any issue, ignoring the request."); + + log.info("Downloading the profile..."); + file.write(outputPath, appleAccount.downloadTeamProvisioningProfile!iOS(team, matchingAppIds[0]).unwrap().encodedProfile); + log.info("Done."); + + return 0; + } +} + diff --git a/frontends/cli/source/certificate.d b/frontends/cli/source/certificate.d new file mode 100644 index 0000000..7dd4405 --- /dev/null +++ b/frontends/cli/source/certificate.d @@ -0,0 +1,190 @@ +module certificate; + +import std.algorithm; +import std.array; +import std.exception; +import std.stdio; +import std.typecons; + +import slf4d; +import slf4d.default_provider; + +import botan.cert.x509.pkcs10; +import botan.filters.data_src; + +import jcli; + +import server.developersession; + +import cli_frontend; + +// @Command("cert", "Manage certificates.") + +@Command("cert list", "List certificates.") +struct ListCerts +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + auto certificates = appleAccount.listAllDevelopmentCerts!iOS(team).unwrap(); + + writefln!"You have %d certificates registered."(certificates.length); + writeln("Currently registered certificates:"); + foreach (certificate; certificates) { + writefln!" - `%s` with the serial number `%s`, from the machine named `%s`."(certificate.name, certificate.serialNumber, certificate.machineName); + } + + return 0; + } +} + +// @Command("cert register", "Register a certificate for Sideloader if we don't already have one.") + +@Command("cert submit", "Submit a certificate signing request to Apple servers.") +struct SubmitCert +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + @ArgPositional("CSR file") + @BindWith!readFile + ubyte[] certificateData; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + auto cert = PKCS10Request(DataSourceMemory(certificateData.ptr, certificateData.length)); + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + appleAccount.submitDevelopmentCSR!iOS(team, cast(string) cert.PEM_encode()).unwrap(); + + return 0; + } +} + + +@Command("cert revoke", "Revoke a certificate.") +struct RevokeCert +{ + mixin LoginCommand; + + @ArgNamed("team", "Team ID") + Nullable!string teamId = null; + + @ArgPositional("certificate serial number") + string serialNumber; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + auto teams = appleAccount.listTeams().unwrap(); + + string teamId = this.teamId.get(null); + if (teamId != null) { + teams = teams.filter!((elem) => elem.teamId == teamId).array(); + } + enforce(teams.length > 0, "No matching team found."); + + auto team = teams[0]; + + auto certificates = appleAccount.listAllDevelopmentCerts!iOS(team).unwrap(); + auto matchingCerts = certificates.filter!((cert) => cert.serialNumber == serialNumber).array(); + + if (matchingCerts.length == 0) { + log.error("No matching certificate found."); + return 1; + } + + enforce(matchingCerts.length == 1, "Multiple certificate matched?? To prevent any issue, ignoring the request."); + + appleAccount.revokeDevelopmentCert!iOS(team, matchingCerts[0]).unwrap(); + + return 0; + } +} diff --git a/frontends/cli/source/cli_frontend.d b/frontends/cli/source/cli_frontend.d new file mode 100644 index 0000000..757498c --- /dev/null +++ b/frontends/cli/source/cli_frontend.d @@ -0,0 +1,218 @@ +module cli_frontend; + +import core.stdc.stdlib; + +import std.array; +import std.datetime; +import std.exception; +import std.format; +import std.path; +import std.process; +import std.stdio; +import std.sumtype; +import std.string; +import std.typecons; +import file = std.file; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +import botan.cert.x509.x509cert; +import botan.pubkey.algo.rsa; + +import plist; + +import provision; + +import imobiledevice; + +import server.appleaccount; +import server.developersession; +import version_string; + +import sideload; +import sideload.bundle; +import sideload.application; +import sideload.certificateidentity; +import sideload.sign; + +import jcli; + +import app; +import utils; + +version = X509; + +auto openApp(string path) { + if (!file.exists(path)) + return fail!Application("The specified app file does not exist."); + + if (!path.endsWith(".ipa")) + return fail!Application("The app is not an ipa file."); + + if (!file.isFile(path)) + return fail!Application("The app should be an ipa file."); + + return ok!Application(new Application(path)); +} + +auto openAppFolder(string path) { + if (!file.exists(path)) + return fail!Application("The specified app file does not exist."); + + if (file.isFile(path)) + return fail!Application("The app should be a folder."); + + return ok!Application(new Application(path)); +} + + +auto readFile(string path) { + return ok!(ubyte[])(cast(ubyte[]) file.read(path)); +} + +auto readPrivateKey(string path) { + RandomNumberGenerator rng = RandomNumberGenerator.makeRng(); + return ok!RSAPrivateKey(RSAPrivateKey(loadKey(path, rng))); +} + +auto readCertificate(string path) { + return X509Certificate(path, false); +} + +extern(C) char* getpass(const(char)* prompt); + +string readPasswordLine(string prompt) { + return fromStringz(cast(immutable) getpass(prompt.toStringz())); +} + +DeveloperSession login(Device device, ADI adi, bool interactive) { + auto log = getLogger(); + + log.info("Logging in..."); + + DeveloperSession account; + + // TODO Keyring stuff + // ... + + if (account) return null; + if (!interactive) { + log.error("You are not logged in. (use `sidestore login` to log-in, or add `-i` to make us ask you the account)"); + return null; + } + + log.info("Please enter your account informations. They will only be sent to Apple servers."); + log.info("See it for yourself at https://github.com/Dadoum/Sideloader/"); + + write("Apple ID: "); + string appleId = readln().chomp(); + string password = readPasswordLine("Password: "); + + return DeveloperSession.login( + device, + adi, + appleId, + password, + (sendCode, submitCode) { + sendCode(); + string code; + do { + write("A code has been sent to your devices, please type it here (type `resend` to resend one): "); + code = readln().chomp(); + if (code == "resend") { + sendCode(); + continue; + } + } while (submitCode(code).match!((Success _) => true, (ReloginNeeded _) => true, (AppleLoginError _) => false)); + }) + .match!( + (DeveloperSession session) => session, + (AppleLoginError error) { + log.errorF!"Can't log-in! %s (%d)"(error.description, error); + return null; + } + ); +} + +// alias BindWith(alias U) = UseConverter!U; + +auto initializeADI(string configurationPath) +{ + scope log = getLogger(); + if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { + auto succeeded = downloadAndInstallDeps(configurationPath, (progress) { + write(format!"%.2f %% completed\r"(progress * 100)); + stdout.flush(); + + return false; + }); + + if (!succeeded) { + log.error("Download failed."); + exit(1); + } + log.info("Download completed."); + } + + scope provisioningData = app.initializeADI(configurationPath); + return provisioningData; +} + +string systemConfigurationPath() +{ + return environment.get("SIDELOADER_CONFIG_DIR").orDefault(defaultConfigurationPath()); +} + +string defaultConfigurationPath() +{ + version (Windows) { + string configurationPath = environment["AppData"]; + } else version (OSX) { + string configurationPath = "~/Library/Preferences/".expandTilde(); + } else { + string configurationPath = environment.get("XDG_CONFIG_DIR") + .orDefault("~/.config") + .expandTilde(); + } + return configurationPath.buildPath("Sideloader"); +} + +// planned commands + +import app_id; +import certificate; +import install; +// @Command("login", "Log-in to your Apple account.") +// @Command("logout", "Log-out.") +import sign; +// @Command("swift-setup", "Set-up certificates to build a Swift Package Manager iOS application (requires SPM in the path).") +import team; +import tool; +// @Command("tweak", "Install a tweak in an ipa file.") + +mixin template LoginCommand() +{ + import provision; + @ArgNamed("i", "Prompt to type passwords if needed.") + bool interactive = false; + + final auto login(Device device, ADI adi) => cli_frontend.login(device, adi, interactive); +} + +@Command("version", "Print the version.") +struct VersionCommand { + void onExecute() { + writeln(versionStr); + } +} + +int main(string[] args) +{ + import keyring; + auto kr = makeKeyring(); + + return new CommandLineInterface!(app_id, certificate, install, sign, team, tool, cli_frontend)().parseAndExecute(args); + // return matchAndExecuteAcrossModules!(app_id, certificate, install, sign, team, tool, cli_frontend)(args); +} diff --git a/frontends/cli/source/install.d b/frontends/cli/source/install.d new file mode 100644 index 0000000..f30e0e5 --- /dev/null +++ b/frontends/cli/source/install.d @@ -0,0 +1,63 @@ +module install; + +import slf4d; +import slf4d.default_provider; + +import jcli; +import progress; + +import imobiledevice; + +import sideload; +import sideload.application; + +import cli_frontend; + +@Command("install", "Install an application on the device (renames the app, register the identifier, sign and install automatically).") +struct InstallCommand +{ + mixin LoginCommand; + + @ArgPositional("app path", "The path of the IPA file to sideload.") + @BindWith!openApp + Application app; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + string udid = iDevice.deviceList()[0].udid; + log.infoF!"Initiating connection the device (UUID: %s)"(udid); + auto device = new iDevice(udid); + Bar progressBar = new Bar(); + string message; + progressBar.message = () => message; + sideloadFull(configurationPath, device, appleAccount, app, (progress, action) { + message = action; + progressBar.index = cast(int) (progress * 100); + progressBar.update(); + }); + progressBar.finish(); + + return 0; + } +} diff --git a/frontends/cli/source/main.d b/frontends/cli/source/main.d deleted file mode 100644 index af13cd8..0000000 --- a/frontends/cli/source/main.d +++ /dev/null @@ -1,109 +0,0 @@ -module main; - -import std.algorithm; -import std.array; -import std.datetime; -import std.format; -import std.path; -import std.stdio; -import std.sumtype; -import std.typecons; -import file = std.file; - -import slf4d; -import slf4d.default_provider; -import slf4d.provider; - -import plist; - -import imobiledevice; - -import server.appleaccount; -import server.developersession; - -import sideload; -import sideload.bundle; -import sideload.application; -import sideload.certificateidentity; - -import app; - -version = X509; - -int main(string[] args) { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - auto configurationPath = "./sideloader-config"; - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - - auto log = getLogger(); - - string appPath; - - if (args.length != 2) { - log.errorF!"Usage: %s "(args.length ? args[0] : "sideloader"); - return 1; - } - appPath = args[1]; - - if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { - auto succeeded = downloadAndInstallDeps(configurationPath, (progress) { - write(format!"%.2f %% completed\r"(progress * 100)); - stdout.flush(); - - return false; - }); - - if (!succeeded) { - log.error("Download failed."); - return 1; - } - log.info("Download completed."); - } - - scope provisioningData = initializeADI(configurationPath); - scope adi = provisioningData.adi; - scope akDevice = provisioningData.device; - scope app = new Application(appPath); - - write("Enter your Apple ID: "); - stdout.flush(); - string appleId = readln()[0..$ - 1]; - write("Enter your password (will appear in clear in your terminal): "); - stdout.flush(); - string password = readln()[0..$ - 1]; - - DeveloperSession appleAccount = DeveloperSession.login( - akDevice, - adi, - appleId, - password, - (sendCode, submitCode) { - sendCode(); - write("A code has been sent to your devices, please write it here: "); - stdout.flush(); - string code = readln(); - submitCode(code); - }).match!( - (DeveloperSession session) => session, - (AppleLoginError error) { - auto errorStr = format!"%s (%d)"(error.description, error); - getLogger().errorF!"Apple auth error: %s"(errorStr); - return null; - } - ); - - if (appleAccount) { - string udid = iDevice.deviceList()[0].udid; - log.infoF!"Initiating connection the device (UUID: %s)"(udid); - auto device = new iDevice(udid); - sideloadFull(configurationPath, device, appleAccount, app, (progress, action) { - log.infoF!"%s (%.2f%%)"(action, progress * 100); - }); - } - - return 0; -} diff --git a/frontends/cli/source/sign.d b/frontends/cli/source/sign.d new file mode 100644 index 0000000..1d7a269 --- /dev/null +++ b/frontends/cli/source/sign.d @@ -0,0 +1,74 @@ +module sign; + +import slf4d; +import slf4d.default_provider; + +import botan.pubkey.algo.rsa; + +import jcli; +import progress; + +import server.developersession; + +import sideload.application; +import sideload.certificateidentity; +import sideload.sign: sideloadSign = sign; + +import cli_frontend; + +@Command("sign", "Sign an application bundle.") +struct SignCommand +{ + @ArgNamed("cert|c", "Certificate (signed by Apple).") + string certificatePath; + + @ArgNamed("key|k", "Private key (matching the signed certificate).") + @BindWith!readPrivateKey + RSAPrivateKey privateKey; + + @ArgNamed("provision|m", "App's provisioning certificate.") + @BindWith!readFile + ubyte[] mobileProvisionFile; + + @ArgPositional("app path", "App path.") + @BindWith!openAppFolder + Application app; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + scope certificate = readCertificate(certificatePath); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + double accumulator = 0; + + log.infoF!"Signing %s..."(app.bundleName()); + Bar progressBar = new Bar(); + double progress = 0; + sideloadSign( + app, + new CertificateIdentity(certificate, privateKey), + [app.bundleIdentifier(): ProvisioningProfile("", "", mobileProvisionFile)], // TODO make a better ctor + (p) { + progressBar.index = cast(int) (progress += p * 100); + progressBar.update(); + } + ); + progressBar.finish(); + + return 0; + } +} diff --git a/frontends/cli/source/team.d b/frontends/cli/source/team.d new file mode 100644 index 0000000..c2fedd9 --- /dev/null +++ b/frontends/cli/source/team.d @@ -0,0 +1,54 @@ +module team; + +import std.algorithm; +import std.array; +import std.exception; +import std.stdio; +import std.typecons; + +import slf4d; +import slf4d.default_provider; + +import jcli; + +import server.developersession; + +import cli_frontend; + +@Command("team list", "List teams.") +struct ListTeams +{ + mixin LoginCommand; + + int onExecute() + { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + auto log = getLogger(); + + string configurationPath = systemConfigurationPath(); + + scope provisioningData = initializeADI(configurationPath); + scope adi = provisioningData.adi; + scope akDevice = provisioningData.device; + + auto appleAccount = login(akDevice, adi); + + if (!appleAccount) { + return 1; + } + + writeln("Teams:"); + auto teams = appleAccount.listTeams().unwrap(); + foreach (team; teams) { + writefln!" - `%s`, with ID `%s`."(team.name, team.teamId); + } + + return 0; + } +} diff --git a/frontends/cli/source/tool.d b/frontends/cli/source/tool.d new file mode 100644 index 0000000..9f563f9 --- /dev/null +++ b/frontends/cli/source/tool.d @@ -0,0 +1,112 @@ +module tool; + +import std.algorithm; +import std.array; +import std.exception; +import std.format; +import std.stdio; +import std.typecons; + +import slf4d; +import slf4d.default_provider; + +import jcli; + +import imobiledevice; + +import tools; + +import cli_frontend; + +@Command("tool list", "List tools.") +struct ListTools +{ + @ArgNamed("udid", "iDevice UDID") + Nullable!string udid = null; + + int onExecute() + { + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + string deviceId; + + if (auto udid = udid.get()) { + deviceId = udid; + } else { + auto deviceList = iDevice.deviceList(); + if (deviceList.length == 0) { + getLogger().error("Please connect a device."); + return 1; + } else if (deviceList.length > 1) { + getLogger().error("Too many devices connected, please use --udid to select the target device."); + return 1; + } + + deviceId = deviceList[0].udid; + } + + iDevice device = new iDevice(deviceId); + + writeln("Available tools:"); + auto tools = toolList(device); + foreach (idx, tool; tools) { + string diag = tool.diagnostic(); + if (diag == null) { + writefln!" - [%d] `%s` tool."(idx, tool.name); + } else { + writefln!" - \033[9m\033[90m[%d] `%s` tool.\033[0m (unavailable: %s)"(idx, tool.name, diag); + } + } + + return 0; + } +} + +@Command("tool run", "Run a tool.") +struct RunTool +{ + @ArgPositional("tool index", "The index of the tool to run (use `tool list` to see these indexes).") + int toolIndex; + + @ArgNamed("udid", "iDevice UDID.") + Nullable!string udid = null; + + int onExecute() + { + configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + + string deviceId; + + if (auto udid = udid.get()) { + deviceId = udid; + } else { + auto deviceList = iDevice.deviceList(); + if (deviceList.length == 0) { + getLogger().error("Please connect a device."); + return 1; + } else if (deviceList.length > 1) { + getLogger().error("Too many devices connected, please use --udid to select the target device."); + return 1; + } + + deviceId = deviceList[0].udid; + } + + iDevice device = new iDevice(deviceId); + + auto tool = toolList(device)[toolIndex]; + if (tool.diagnostic != null) { + getLogger().errorF!"The tool cannot be run: %s"(tool.diagnostic); + return 1; + } + + tool.run((message, canCancel) { + message = format!"%s [press return to continue]%s"(message, canCancel ? " [press ^C to quit]" : ""); + stdout.writeln(message); + readln(); + return true; + }); + + return 0; + } +} diff --git a/frontends/common/linux/secret/BackendIF.d b/frontends/common/linux/secret/BackendIF.d deleted file mode 100644 index ddbb503..0000000 --- a/frontends/common/linux/secret/BackendIF.d +++ /dev/null @@ -1,87 +0,0 @@ -module secret.BackendIF; - -private import gio.AsyncResultIF; -private import gio.Cancellable; -private import glib.ErrorG; -private import glib.GException; -private import gobject.ObjectG; -private import secret.BackendIF; -private import secret.c.functions; -public import secret.c.types; - - -/** - * #SecretBackend represents a backend implementation of password - * storage. - * - * Since: 0.19.0 - */ -public interface BackendIF{ - /** Get the main Gtk struct */ - public SecretBackend* getBackendStruct(bool transferOwnership = false); - - /** the main Gtk struct as a void* */ - protected void* getStruct(); - - - /** */ - public static GType getType() - { - return secret_backend_get_type(); - } - - /** - * Get a #SecretBackend instance. - * - * If such a backend already exists, then the same backend is returned. - * - * If @flags contains any flags of which parts of the secret backend to - * ensure are initialized, then those will be initialized before completing. - * - * This method will return immediately and complete asynchronously. - * - * Params: - * flags = flags for which service functionality to ensure is initialized - * cancellable = optional cancellation object - * callback = called when the operation completes - * userData = data to be passed to the callback - * - * Since: 0.19.0 - */ - public static void get(SecretBackendFlags flags, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) - { - secret_backend_get(flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); - } - - /** - * Complete an asynchronous operation to get a #SecretBackend. - * - * Params: - * result = the asynchronous result passed to the callback - * - * Returns: a new reference to a #SecretBackend proxy, which - * should be released with [method@GObject.Object.unref]. - * - * Since: 0.19.0 - * - * Throws: GException on failure. - */ - public static BackendIF getFinish(AsyncResultIF result) - { - GError* err = null; - - auto __p = secret_backend_get_finish((result is null) ? null : result.getAsyncResultStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } - - if(__p is null) - { - return null; - } - - return ObjectG.getDObject!(BackendIF)(cast(SecretBackend*) __p, true); - } -} diff --git a/frontends/common/linux/secret/RetrievableIF.d b/frontends/common/linux/secret/RetrievableIF.d deleted file mode 100644 index f5319d4..0000000 --- a/frontends/common/linux/secret/RetrievableIF.d +++ /dev/null @@ -1,144 +0,0 @@ -module secret.RetrievableIF; - -private import gio.AsyncResultIF; -private import gio.Cancellable; -private import glib.ErrorG; -private import glib.GException; -private import glib.HashTable; -private import glib.Str; -private import glib.c.functions; -private import gobject.ObjectG; -private import secret.Value; -private import secret.c.functions; -public import secret.c.types; - - -/** - * A read-only view of a secret item in the Secret Service. - * - * #SecretRetrievable provides a read-only view of a secret item - * stored in the Secret Service. - * - * Each item has a value, represented by a [struct@Value], which can be - * retrieved by [method@Retrievable.retrieve_secret] and - * [method@Retrievable.retrieve_secret_finish]. - * - * Since: 0.19.0 - */ -public interface RetrievableIF{ - /** Get the main Gtk struct */ - public SecretRetrievable* getRetrievableStruct(bool transferOwnership = false); - - /** the main Gtk struct as a void* */ - protected void* getStruct(); - - - /** */ - public static GType getType() - { - return secret_retrievable_get_type(); - } - - /** - * Get the attributes of this object. - * - * The attributes are a mapping of string keys to string values. - * Attributes are used to search for items. Attributes are not stored - * or transferred securely by the secret service. - * - * Do not modify the attribute returned by this method. - * - * Returns: a new reference - * to the attributes, which should not be modified, and - * released with [func@GLib.HashTable.unref] - * - * Since: 0.19.0 - */ - public HashTable getAttributes(); - - /** - * Get the created date and time of the object. - * - * The return value is the number of seconds since the unix epoch, January 1st - * 1970. - * - * Returns: the created date and time - * - * Since: 0.19.0 - */ - public ulong getCreated(); - - /** - * Get the label of this item. - * - * Returns: the label, which should be freed with [func@GLib.free] - * - * Since: 0.19.0 - */ - public string getLabel(); - - /** - * Get the modified date and time of the object. - * - * The return value is the number of seconds since the unix epoch, January 1st - * 1970. - * - * Returns: the modified date and time - * - * Since: 0.19.0 - */ - public ulong getModified(); - - /** - * Retrieve the secret value of this object. - * - * Each retrievable object has a single secret which might be a - * password or some other secret binary value. - * - * This function returns immediately and completes asynchronously. - * - * Params: - * cancellable = optional cancellation object - * callback = called when the operation completes - * userData = data to pass to the callback - * - * Since: 0.19.0 - */ - public void retrieveSecret(Cancellable cancellable, GAsyncReadyCallback callback, void* userData); - - /** - * Complete asynchronous operation to retrieve the secret value of this object. - * - * Params: - * result = asynchronous result passed to callback - * - * Returns: the secret value which should be - * released with [method@Value.unref], or %NULL - * - * Since: 0.19.0 - * - * Throws: GException on failure. - */ - public Value retrieveSecretFinish(AsyncResultIF result); - - /** - * Retrieve the secret value of this object synchronously. - * - * Each retrievable object has a single secret which might be a - * password or some other secret binary value. - * - * This method may block indefinitely and should not be used in user interface - * threads. - * - * Params: - * cancellable = optional cancellation object - * - * Returns: the secret value which should be - * released with [method@Value.unref], or %NULL - * - * Since: 0.19.0 - * - * Throws: GException on failure. - */ - public Value retrieveSecretSync(Cancellable cancellable); -} diff --git a/frontends/common/linux/secret/Schema.d b/frontends/common/linux/secret/Schema.d deleted file mode 100644 index 18a5fca..0000000 --- a/frontends/common/linux/secret/Schema.d +++ /dev/null @@ -1,190 +0,0 @@ -module secret.Schema; - -private import glib.ConstructionException; -private import glib.HashTable; -private import glib.Str; -private import gobject.ObjectG; -private import linker.Loader; -private import secret.c.functions; -public import secret.c.types; - - -/** - * Represents a set of attributes that are stored with an item. - * - * These schemas are used for interoperability between various services storing - * the same types of items. - * - * Each schema has a name like `org.gnome.keyring.NetworkPassword`, and defines a - * set of attributes, and types (string, integer, boolean) for those attributes. - * - * Attributes are stored as strings in the Secret Service, and the attribute types - * simply define standard ways to store integer and boolean values as strings. - * Attributes are represented in libsecret via a [struct@GLib.HashTable] with - * string keys and values. Even for values that defined as an integer or boolean in - * the schema, the attribute values in the [struct@GLib.HashTable] are strings. - * Boolean values are stored as the strings 'true' and 'false'. Integer values are - * stored in decimal, with a preceding negative sign for negative integers. - * - * Schemas are handled entirely on the client side by this library. The name of the - * schema is automatically stored as an attribute on the item. - * - * Normally when looking up passwords only those with matching schema names are - * returned. If the schema @flags contain the `SECRET_SCHEMA_DONT_MATCH_NAME` flag, - * then lookups will not check that the schema name matches that on the item, only - * the schema's attributes are matched. This is useful when you are looking up - * items that are not stored by the libsecret library. Other libraries such as - * libgnome-keyring don't store the schema name. - * - * Additional schemas can be defined via the %SecretSchema structure like this: - * - * ```c - * // in a header: - * - * const SecretSchema * example_get_schema (void) G_GNUC_CONST; - * - * #define EXAMPLE_SCHEMA example_get_schema () - * - * - * // in a .c file - * - * const SecretSchema * - * example_get_schema (void) - * { - * static const SecretSchema the_schema = { - * "org.example.Password", SECRET_SCHEMA_NONE, - * { - * { "number", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, - * { "string", SECRET_SCHEMA_ATTRIBUTE_STRING }, - * { "even", SECRET_SCHEMA_ATTRIBUTE_BOOLEAN }, - * { NULL, 0 }, - * } - * }; - * return &the_schema; - * } - * ``` - */ -public class Schema -{ - /** the main Gtk struct */ - protected SecretSchema* secretSchema; - protected bool ownedRef; - - /** Get the main Gtk struct */ - public SecretSchema* getSchemaStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretSchema; - } - - /** the main Gtk struct as a void* */ - protected void* getStruct() - { - return cast(void*)secretSchema; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretSchema* secretSchema, bool ownedRef = false) - { - this.secretSchema = secretSchema; - this.ownedRef = ownedRef; - } - - ~this () - { - if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) - secret_schema_unref(secretSchema); - } - - - /** */ - public static GType getType() - { - return secret_schema_get_type(); - } - - /** - * Using this function is not normally necessary from C code. This is useful - * for constructing #SecretSchema structures in bindings. - * - * A schema represents a set of attributes that are stored with an item. These - * schemas are used for interoperability between various services storing the - * same types of items. - * - * Each schema has an @name like `org.gnome.keyring.NetworkPassword`, and - * defines a set of attributes names, and types (string, integer, boolean) for - * those attributes. - * - * Each key in the @attributes table should be a attribute name strings, and - * the values in the table should be integers from the [enum@SchemaAttributeType] - * enumeration, representing the attribute type for each attribute name. - * - * Normally when looking up passwords only those with matching schema names are - * returned. If the schema @flags contain the %SECRET_SCHEMA_DONT_MATCH_NAME flag, - * then lookups will not check that the schema name matches that on the item, only - * the schema's attributes are matched. This is useful when you are looking up items - * that are not stored by the libsecret library. Other libraries such as libgnome-keyring - * don't store the schema name. - * - * Params: - * name = the dotted name of the schema - * flags = the flags for the schema - * attributeNamesAndTypes = the attribute names and types of those attributes - * - * Returns: the new schema, which should be unreferenced with - * [method@Schema.unref] when done - * - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(string name, SecretSchemaFlags flags, HashTable attributeNamesAndTypes) - { - auto __p = secret_schema_newv(Str.toStringz(name), flags, (attributeNamesAndTypes is null) ? null : attributeNamesAndTypes.getHashTableStruct()); - - if(__p is null) - { - throw new ConstructionException("null returned by newv"); - } - - this(cast(SecretSchema*) __p); - } - - alias doref = ref_; - /** - * Adds a reference to the #SecretSchema. - * - * It is not normally necessary to call this function from C code, and is - * mainly present for the sake of bindings. If the @schema was statically - * allocated, then this function will copy the schema. - * - * Returns: the referenced schema, which should be later - * unreferenced with [method@Schema.unref] - */ - public Schema ref_() - { - auto __p = secret_schema_ref(secretSchema); - - if(__p is null) - { - return null; - } - - return ObjectG.getDObject!(Schema)(cast(SecretSchema*) __p, true); - } - - /** - * Releases a reference to the #SecretSchema. - * - * If the last reference is released then the schema will be freed. - * - * It is not normally necessary to call this function from C code, and is - * mainly present for the sake of bindings. It is an error to call this for - * a @schema that was statically allocated. - */ - public void unref() - { - secret_schema_unref(secretSchema); - } -} diff --git a/frontends/common/linux/secret/SchemaAttribute.d b/frontends/common/linux/secret/SchemaAttribute.d deleted file mode 100644 index 39dfbb8..0000000 --- a/frontends/common/linux/secret/SchemaAttribute.d +++ /dev/null @@ -1,83 +0,0 @@ -module secret.SchemaAttribute; - -private import glib.MemorySlice; -private import glib.Str; -private import glib.c.functions; -private import linker.Loader; -private import secret.c.functions; -public import secret.c.types; - - -/** - * An attribute in a #SecretSchema. - */ -public final class SchemaAttribute -{ - /** the main Gtk struct */ - protected SecretSchemaAttribute* secretSchemaAttribute; - protected bool ownedRef; - - /** Get the main Gtk struct */ - public SecretSchemaAttribute* getSchemaAttributeStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretSchemaAttribute; - } - - /** the main Gtk struct as a void* */ - protected void* getStruct() - { - return cast(void*)secretSchemaAttribute; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretSchemaAttribute* secretSchemaAttribute, bool ownedRef = false) - { - this.secretSchemaAttribute = secretSchemaAttribute; - this.ownedRef = ownedRef; - } - - ~this () - { - if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) - sliceFree(secretSchemaAttribute); - } - - - /** - * name of the attribute - */ - public @property string name() - { - return Str.toString(secretSchemaAttribute.name); - } - - /** Ditto */ - public @property void name(string value) - { - secretSchemaAttribute.name = Str.toStringz(value); - } - - /** - * the type of the attribute - */ - public @property SecretSchemaAttributeType type() - { - return secretSchemaAttribute.type; - } - - /** Ditto */ - public @property void type(SecretSchemaAttributeType value) - { - secretSchemaAttribute.type = value; - } - - /** */ - public static GType getType() - { - return secret_schema_attribute_get_type(); - } -} diff --git a/frontends/common/linux/secret/Value.d b/frontends/common/linux/secret/Value.d deleted file mode 100644 index a122791..0000000 --- a/frontends/common/linux/secret/Value.d +++ /dev/null @@ -1,219 +0,0 @@ -module secret.Value; - -private import glib.ConstructionException; -private import glib.Str; -private import glib.c.functions; -private import gobject.ObjectG; -private import linker.Loader; -private import secret.c.functions; -public import secret.c.types; - - -/** - * A value containing a secret - * - * A #SecretValue contains a password or other secret value. - * - * Use [method@Value.get] to get the actual secret data, such as a password. - * The secret data is not necessarily null-terminated, unless the content type - * is "text/plain". - * - * Each #SecretValue has a content type. For passwords, this is `text/plain`. - * Use [method@Value.get_content_type] to look at the content type. - * - * #SecretValue is reference counted and immutable. The secret data is only - * freed when all references have been released via [method@Value.unref]. - */ -public class Value -{ - /** the main Gtk struct */ - protected SecretValue* secretValue; - protected bool ownedRef; - - /** Get the main Gtk struct */ - public SecretValue* getValueStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretValue; - } - - /** the main Gtk struct as a void* */ - protected void* getStruct() - { - return cast(void*)secretValue; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretValue* secretValue, bool ownedRef = false) - { - this.secretValue = secretValue; - this.ownedRef = ownedRef; - } - - ~this () - { - if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) - secret_value_unref(secretValue); - } - - - /** */ - public static GType getType() - { - return secret_value_get_type(); - } - - /** - * Create a #SecretValue for the secret data passed in. - * - * The secret data is copied into non-pageable 'secure' memory. - * - * If the length is less than zero, then @secret is assumed to be - * null-terminated. - * - * Params: - * secret = the secret data - * length = the length of the data - * contentType = the content type of the data - * - * Returns: the new #SecretValue - * - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(string secret, ptrdiff_t length, string contentType) - { - auto __p = secret_value_new(Str.toStringz(secret), length, Str.toStringz(contentType)); - - if(__p is null) - { - throw new ConstructionException("null returned by new"); - } - - this(cast(SecretValue*) __p); - } - - /** - * Create a #SecretValue for the secret data passed in. - * - * The secret data is not copied, and will later be freed with the @destroy - * function. - * - * If the length is less than zero, then @secret is assumed to be - * null-terminated. - * - * Params: - * secret = the secret data - * length = the length of the data - * contentType = the content type of the data - * destroy = function to call to free the secret data - * - * Returns: the new #SecretValue - * - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(string secret, ptrdiff_t length, string contentType, GDestroyNotify destroy) - { - auto __p = secret_value_new_full(Str.toStringz(secret), length, Str.toStringz(contentType), destroy); - - if(__p is null) - { - throw new ConstructionException("null returned by new_full"); - } - - this(cast(SecretValue*) __p); - } - - /** - * Get the secret data in the #SecretValue. - * - * The value is not necessarily null-terminated unless it was created with - * [ctor@Value.new] or a null-terminated string was passed to - * [ctor@Value.new_full]. - * - * Returns: the secret data - */ - public string get() - { - size_t length; - - return Str.toString(secret_value_get(secretValue, &length)); - } - - /** - * Get the content type of the secret value, such as - * `text/plain`. - * - * Returns: the content type - */ - public string getContentType() - { - return Str.toString(secret_value_get_content_type(secretValue)); - } - - /** - * Get the secret data in the #SecretValue if it contains a textual - * value. - * - * The content type must be `text/plain`. - * - * Returns: the content type - */ - public string getText() - { - return Str.toString(secret_value_get_text(secretValue)); - } - - alias doref = ref_; - /** - * Add another reference to the #SecretValue. - * - * For each reference [method@Value.unref] should be called to unreference the - * value. - * - * Returns: the value - */ - public Value ref_() - { - auto __p = secret_value_ref(secretValue); - - if(__p is null) - { - return null; - } - - return ObjectG.getDObject!(Value)(cast(SecretValue*) __p, true); - } - - /** - * Unreference a #SecretValue. - * - * When the last reference is gone, then the value will be freed. - */ - public void unref() - { - secret_value_unref(secretValue); - } - - /** - * Unreference a #SecretValue and steal the secret data in - * #SecretValue as nonpageable memory. - * - * Params: - * length = the length of the secret - * - * Returns: a new password string stored in nonpageable memory - * which must be freed with [func@password_free] when done - * - * Since: 0.19.0 - */ - public string unrefToPassword(ref size_t length) - { - auto retStr = secret_value_unref_to_password(secretValue, &length); - - scope(exit) Str.freeString(retStr); - return Str.toString(retStr); - } -} diff --git a/frontends/common/linux/secretapi.d b/frontends/common/linux/secretapi.d deleted file mode 100644 index 331c226..0000000 --- a/frontends/common/linux/secretapi.d +++ /dev/null @@ -1,2 +0,0 @@ -module secretapi; - diff --git a/frontends/common/windows/graphical_app.d b/frontends/common/windows/graphical_app.d index 427321a..6efe4ae 100644 --- a/frontends/common/windows/graphical_app.d +++ b/frontends/common/windows/graphical_app.d @@ -4,10 +4,10 @@ import core.sys.windows.windef; public import core.sys.windows.winbase: SetUnhandledExceptionFilter; pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); -static if (__VERSION__ >= 2091) - pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); -else - pragma(linkerDirective, "/ENTRY:mainCRTStartup"); +// static if (__VERSION__ >= 2091) +// pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); +// else +// pragma(linkerDirective, "/ENTRY:mainCRTStartup"); private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { this(string file = __FILE__, size_t line = __LINE__) { diff --git a/frontends/dlangui/source/main.d b/frontends/dlangui/source/main.d index 735a189..8f1a10e 100644 --- a/frontends/dlangui/source/main.d +++ b/frontends/dlangui/source/main.d @@ -52,7 +52,7 @@ extern (C) int UIAppMain() { } version (Windows) { - string configurationPath = environment["LocalAppData"]; + string configurationPath = environment["AppData"]; } else version (OSX) { string configurationPath = "~/Library/Preferences/".expandTilde(); } else { diff --git a/frontends/dlangui/source/ui/dependenciesframe.d b/frontends/dlangui/source/ui/dependenciesframe.d index 90ca27e..0728168 100644 --- a/frontends/dlangui/source/ui/dependenciesframe.d +++ b/frontends/dlangui/source/ui/dependenciesframe.d @@ -7,19 +7,51 @@ import slf4d; import dlangui; +import app; + class DependenciesFrame: VerticalLayout { - this() { - addChild(new TextWidget(null, "Deps needed plz download"d)); - addChild(new Button(null, "Yes plz"d)); + this(string configurationPath, void delegate() onCompletion) { + auto log = getLogger(); + + addChild(new TextWidget(null, "A ~130 MB download is required. This will require 5 MB on your computer."d)); + ProgressBarWidget progressBar = new ProgressBarWidget(); + progressBar.animationInterval = 50; + Button button = new Button(null, "Proceed"d); + button.click = (_) { + import core.thread; + auto win = window(); + new Thread({ + log.info("Downloading Apple's APK."); + // auto succeeded = downloadAndInstallDeps(configurationPath, (progress) { + // executeInUiThread({ + // progressBar.progress(cast(int) (progress * 1000)); + // }); + // return win.windowState() == WindowState.hidden; + // }); + auto succeeded = true; + + if (succeeded) { + log.info("Download successful."); + executeInUiThread({ + onCompletion(); + win.close(); + }); + } + }).start(); + return true; + }; + + addChild(button); + addChild(progressBar); } static void ensureDeps(string configurationPath, void delegate() onCompletion) { if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { // Missing dependencies - auto tfaWindow = Platform.instance.createWindow("Download required.", null, WindowFlag.ExpandSize, 1, 1); - tfaWindow.mainWidget = new DependenciesFrame(); - tfaWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; - tfaWindow.show(); + auto depWindow = Platform.instance.createWindow("Download required.", null, WindowFlag.ExpandSize, 1, 1); + depWindow.mainWidget = new DependenciesFrame(configurationPath, onCompletion); + depWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; + depWindow.show(); } else { onCompletion(); } diff --git a/frontends/dlangui/source/ui/mainframe.d b/frontends/dlangui/source/ui/mainframe.d index c5db7ac..a167244 100644 --- a/frontends/dlangui/source/ui/mainframe.d +++ b/frontends/dlangui/source/ui/mainframe.d @@ -258,7 +258,7 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ installButton.enabled = false; installButton.click = (_) { // TODO - // LoginFrame.login(null, null, window(), (_) {}); + LoginFrame.login(null, null, window(), (_) {}); return true; }; installFrame.addChild(installButton); diff --git a/frontends/gtk/source/ui/authentication/loginslide.d b/frontends/gtk/source/ui/authentication/loginslide.d index 45c5f27..5d7fd04 100644 --- a/frontends/gtk/source/ui/authentication/loginslide.d +++ b/frontends/gtk/source/ui/authentication/loginslide.d @@ -107,13 +107,20 @@ class LoginSlide: Box, AssistantSlide { new Thread({ uiTry!({ - DeveloperSession appleAccount = DeveloperSession.login( - runningApplication.device, - runningApplication.adi, + auto device = runningApplication.device; + auto adi = runningApplication.adi; + + scope(failure) runInUIThread({ + authAssistant.setSensitive(true); + authAssistant.setCursor(authAssistant.defaultCursor); + }); + + auto appleAccount = DeveloperSession.login( + device, + adi, appleId, password, - (sendCode, submitCode) { - + (sendCode, submitCode) { auto tid = thisTid; runInUIThread({ authAssistant.next(new TFASlide(authAssistant, tid, sendCode, submitCode)); diff --git a/frontends/gtk/source/ui/managecertificateswindow.d b/frontends/gtk/source/ui/managecertificateswindow.d index 4283888..847978b 100644 --- a/frontends/gtk/source/ui/managecertificateswindow.d +++ b/frontends/gtk/source/ui/managecertificateswindow.d @@ -2,12 +2,16 @@ module ui.managecertificateswindow; import core.thread; +import file = std.file; + import adw.ActionRow; import adw.ExpanderRow; import gdk.Cursor; import gtk.Dialog; +import gtk.FileChooserNative; +import gtk.FileFilter; import gtk.ListBox; import gtk.ScrolledWindow; import gtk.Window; @@ -75,6 +79,34 @@ class ManageCertificatesWindow: Dialog { }); this.addRow(revokeApplicationRow); + ActionRow dumpApplicationRow = new ActionRow(); + dumpApplicationRow.setTitle("Dump"); + dumpApplicationRow.setActivatable(true); + dumpApplicationRow.addOnActivated((_) { + auto fileChooser = new FileChooserNative( + "Save certificate", + window, + FileChooserAction.SAVE, + "_Save", + "_Cancel" + ); + fileChooser.setTransientFor(window); + fileChooser.setModal(true); + auto mpFilter = new FileFilter(); + mpFilter.addPattern("*.der"); + mpFilter.addSuffix(".der"); + mpFilter.setName("X509 certificate"); + fileChooser.addFilter(mpFilter); + fileChooser.setCurrentName(certificate.machineName ~ ".der"); + fileChooser.addOnResponse((response, _) { + if (response == ResponseType.ACCEPT) { + string path = fileChooser.getFile().getPath(); + file.write(path, certificate.certContent); + } + }); + fileChooser.show(); + }); + this.addRow(dumpApplicationRow); } } } diff --git a/keyring/.gitignore b/keyring/.gitignore new file mode 100644 index 0000000..a45a217 --- /dev/null +++ b/keyring/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/keyring +keyring.so +keyring.dylib +keyring.dll +keyring.a +keyring.lib +keyring-test-* +*.exe +*.pdb +*.o +*.obj +*.lst diff --git a/keyring/dub.json b/keyring/dub.json new file mode 100644 index 0000000..e51e855 --- /dev/null +++ b/keyring/dub.json @@ -0,0 +1,56 @@ +{ + "name": "keyring", + "description": "A cross-platform library to store secret information safely for Sideloader (has some constants for it).", + "authors": [ + "Dadoum" + ], + "license": "LGPL-2.1-or-later", + "copyright": "Copyright © 2023, Dadoum", + "targetType": "staticLibrary", + + "sourceFiles": [ + "../source/utils.d" + ], + + "dependencies": { + "slf4d": "~>2" + }, + + "configurations": [ + { + "name": "windows", + "platforms": ["windows"], + + "dependencies": { + "windows-headers": "~>1.0.5" + } + }, + { + "name": "macOS", + "platforms": ["osx"], + + "dependencies": {} + }, + { + "name": "libsecret", + "platforms": ["linux"], + "versions": ["LibSecret"], + + "sourcePaths": [ + "libsecret/" + ], + + "importPaths": [ + "libsecret/" + ], + + "dependencies": { + "gtk_d:gio": "~>1.0.3", + "gtk_d:glib": "~>1.0.3" + } + }, + { + "name": "none" + } + ] +} \ No newline at end of file diff --git a/keyring/dub.selections.json b/keyring/dub.selections.json new file mode 100644 index 0000000..586d666 --- /dev/null +++ b/keyring/dub.selections.json @@ -0,0 +1,8 @@ +{ + "fileVersion": 1, + "versions": { + "gtk_d": "1.0.3", + "slf4d": "2.4.3", + "windows-headers": "1.0.5" + } +} diff --git a/keyring/libsecret/APILookup.txt b/keyring/libsecret/APILookup.txt new file mode 100644 index 0000000..c5fd606 --- /dev/null +++ b/keyring/libsecret/APILookup.txt @@ -0,0 +1,38 @@ +wrap: secret +file: Secret-1.gir + +move: attributes_build Attributes build +move: attributes_buildv Attributes buildv +move: get_schema Schema +move: password_clear Password clear +move: password_clear_finish Password clear_finish +move: password_clear_sync Password clear_sync +move: password_clearv Password clearv +move: password_clearv_sync Password clearv_sync +move: password_free Password free +move: password_lookup Password lookup +move: password_lookup_binary_finish Password lookup_binary_finish +move: password_lookup_binary_sync Password lookup_binary_sync +move: password_lookup_finish Password lookup_finish +move: password_lookup_nonpageable_finish Password lookup_nonpageable_finish +move: password_lookup_nonpageable_sync Password lookup_nonpageable_sync +move: password_lookup_sync Password lookup_sync +move: password_lookupv Password lookupv +move: password_lookupv_binary_sync Password lookupv_binary_sync +move: password_lookupv_nonpageable_sync Password lookupv_nonpageable_sync +move: password_lookupv_sync Password lookupv_sync +move: password_search Password search +move: password_search_finish Password search_finish +move: password_search_sync Password search_sync +move: password_searchv Password searchv +move: password_searchv_sync Password searchv_sync +move: password_store Password store +move: password_store_binary Password store_binary +move: password_store_binary_sync Password store_binary_sync +move: password_store_finish Password store_finish +move: password_store_sync Password store_sync +move: password_storev Password storev +move: password_storev_binary Password storev_binary +move: password_storev_binary_sync Password storev_binary_sync +move: password_storev_sync Password storev_sync +move: password_wipe Password wipe diff --git a/keyring/libsecret/secret/Attributes.d b/keyring/libsecret/secret/Attributes.d new file mode 100644 index 0000000..2d4f0ac --- /dev/null +++ b/keyring/libsecret/secret/Attributes.d @@ -0,0 +1,39 @@ +module secret.Attributes; + +private import glib.HashTable; +private import secret.Schema; +private import secret.c.functions; +public import secret.c.types; + + +/** */ +public struct Attributes +{ + + /** + * Build up a hash table of attribute values. + * + * The variable argument list should contain pairs of a) The attribute name as + * a null-terminated string, followed by b) attribute value, either a character + * string, an int number, or a gboolean value, as defined in the password + * @schema. The list of attributes should be terminated with a %NULL. + * + * Params: + * schema = the schema for the attributes + * va = the attribute keys and values, terminated with %NULL + * + * Returns: a new table of + * attributes, to be released with [func@GLib.HashTable.unref] + */ + public static HashTable buildv(Schema schema, void* va) + { + auto __p = secret_attributes_buildv((schema is null) ? null : schema.getSchemaStruct(), va); + + if(__p is null) + { + return null; + } + + return new HashTable(cast(GHashTable*) __p, true); + } +} diff --git a/keyring/libsecret/secret/BackendIF.d b/keyring/libsecret/secret/BackendIF.d new file mode 100644 index 0000000..a2936db --- /dev/null +++ b/keyring/libsecret/secret/BackendIF.d @@ -0,0 +1,87 @@ +module secret.BackendIF; + +private import gio.AsyncResultIF; +private import gio.Cancellable; +private import glib.ErrorG; +private import glib.GException; +private import gobject.ObjectG; +private import secret.BackendIF; +private import secret.c.functions; +public import secret.c.types; + + +/** + * #SecretBackend represents a backend implementation of password + * storage. + * + * Since: 0.19.0 + */ +public interface BackendIF{ + /** Get the main Gtk struct */ + public SecretBackend* getBackendStruct(bool transferOwnership = false); + + /** the main Gtk struct as a void* */ + protected void* getStruct(); + + + /** */ + public static GType getType() + { + return secret_backend_get_type(); + } + + /** + * Get a #SecretBackend instance. + * + * If such a backend already exists, then the same backend is returned. + * + * If @flags contains any flags of which parts of the secret backend to + * ensure are initialized, then those will be initialized before completing. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * flags = flags for which service functionality to ensure is initialized + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + * + * Since: 0.19.0 + */ + public static void get(SecretBackendFlags flags, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_backend_get(flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Complete an asynchronous operation to get a #SecretBackend. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a new reference to a #SecretBackend proxy, which + * should be released with [method@GObject.Object.unref]. + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static BackendIF getFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_backend_get_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(BackendIF)(cast(SecretBackend*) __p, true); + } +} diff --git a/frontends/common/linux/secret/BackendT.d b/keyring/libsecret/secret/BackendT.d similarity index 65% rename from frontends/common/linux/secret/BackendT.d rename to keyring/libsecret/secret/BackendT.d index f7a8681..c761197 100644 --- a/frontends/common/linux/secret/BackendT.d +++ b/keyring/libsecret/secret/BackendT.d @@ -18,12 +18,12 @@ public import secret.c.types; */ public template BackendT(TStruct) { - /** Get the main Gtk struct */ - public SecretBackend* getBackendStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return cast(SecretBackend*)getStruct(); - } + /** Get the main Gtk struct */ + public SecretBackend* getBackendStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return cast(SecretBackend*)getStruct(); + } } diff --git a/frontends/common/linux/secret/Collection.d b/keyring/libsecret/secret/Collection.d similarity index 89% rename from frontends/common/linux/secret/Collection.d rename to keyring/libsecret/secret/Collection.d index 5050df6..69f2fcb 100644 --- a/frontends/common/linux/secret/Collection.d +++ b/keyring/libsecret/secret/Collection.d @@ -39,99 +39,99 @@ public import secret.c.types; */ public class Collection : DBusProxy { - /** the main Gtk struct */ - protected SecretCollection* secretCollection; - - /** Get the main Gtk struct */ - public SecretCollection* getCollectionStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretCollection; - } - - /** the main Gtk struct as a void* */ - protected override void* getStruct() - { - return cast(void*)secretCollection; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretCollection* secretCollection, bool ownedRef = false) - { - this.secretCollection = secretCollection; - super(cast(GDBusProxy*)secretCollection, ownedRef); - } - - - /** */ - public static GType getType() - { - return secret_collection_get_type(); - } - - /** - * Finish asynchronous operation to get a new collection proxy for a - * collection in the secret service. - * - * Params: - * result = the asynchronous result passed to the callback - * - * Returns: the new collection, which should be unreferenced - * with [method@GObject.Object.unref] - * - * Throws: GException on failure. - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(AsyncResultIF result) - { - GError* err = null; - - auto __p = secret_collection_new_for_dbus_path_finish((result is null) ? null : result.getAsyncResultStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } - - if(__p is null) - { - throw new ConstructionException("null returned by new_for_dbus_path_finish"); - } - - this(cast(SecretCollection*) __p, true); - } - - /** - * Get a new collection proxy for a collection in the secret service. - * - * If @service is %NULL, then [func@Service.get_sync] will be called to get - * the default [class@Service] proxy. - * - * This method may block indefinitely and should not be used in user interface - * threads. - * - * Params: - * service = a secret service object - * collectionPath = the D-Bus path of the collection - * flags = options for the collection initialization - * cancellable = optional cancellation object - * - * Returns: the new collection, which should be unreferenced - * with [method@GObject.Object.unref] - * - * Throws: GException on failure. - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(Service service, string collectionPath, SecretCollectionFlags flags, Cancellable cancellable) - { - GError* err = null; - - auto __p = secret_collection_new_for_dbus_path_sync((service is null) ? null : service.getServiceStruct(), Str.toStringz(collectionPath), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); - - if (err !is null) + /** the main Gtk struct */ + protected SecretCollection* secretCollection; + + /** Get the main Gtk struct */ + public SecretCollection* getCollectionStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretCollection; + } + + /** the main Gtk struct as a void* */ + protected override void* getStruct() + { + return cast(void*)secretCollection; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretCollection* secretCollection, bool ownedRef = false) + { + this.secretCollection = secretCollection; + super(cast(GDBusProxy*)secretCollection, ownedRef); + } + + + /** */ + public static GType getType() + { + return secret_collection_get_type(); + } + + /** + * Finish asynchronous operation to get a new collection proxy for a + * collection in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: the new collection, which should be unreferenced + * with [method@GObject.Object.unref] + * + * Throws: GException on failure. + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_collection_new_for_dbus_path_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + throw new ConstructionException("null returned by new_for_dbus_path_finish"); + } + + this(cast(SecretCollection*) __p, true); + } + + /** + * Get a new collection proxy for a collection in the secret service. + * + * If @service is %NULL, then [func@Service.get_sync] will be called to get + * the default [class@Service] proxy. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * service = a secret service object + * collectionPath = the D-Bus path of the collection + * flags = options for the collection initialization + * cancellable = optional cancellation object + * + * Returns: the new collection, which should be unreferenced + * with [method@GObject.Object.unref] + * + * Throws: GException on failure. + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(Service service, string collectionPath, SecretCollectionFlags flags, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_collection_new_for_dbus_path_sync((service is null) ? null : service.getServiceStruct(), Str.toStringz(collectionPath), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) { throw new GException( new ErrorG(err) ); } @@ -445,8 +445,6 @@ public class Collection : DBusProxy return secret_collection_get_created(secretCollection); } - alias getFlags = DBusProxy.getFlags; - /** * Get the flags representing what features of the #SecretCollection proxy * have been initialized. @@ -460,6 +458,7 @@ public class Collection : DBusProxy { return secret_collection_get_flags(secretCollection); } + alias getFlags = DBusProxy.getFlags; /** * Get the list of items in this collection. diff --git a/frontends/common/linux/secret/Item.d b/keyring/libsecret/secret/Item.d similarity index 88% rename from frontends/common/linux/secret/Item.d rename to keyring/libsecret/secret/Item.d index 4457c38..56571e2 100644 --- a/frontends/common/linux/secret/Item.d +++ b/keyring/libsecret/secret/Item.d @@ -51,105 +51,105 @@ public import secret.c.types; */ public class Item : DBusProxy, RetrievableIF { - /** the main Gtk struct */ - protected SecretItem* secretItem; - - /** Get the main Gtk struct */ - public SecretItem* getItemStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretItem; - } - - /** the main Gtk struct as a void* */ - protected override void* getStruct() - { - return cast(void*)secretItem; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretItem* secretItem, bool ownedRef = false) - { - this.secretItem = secretItem; - super(cast(GDBusProxy*)secretItem, ownedRef); - } - - // add the Retrievable capabilities - mixin RetrievableT!(SecretItem); - - - /** */ - public static GType getType() - { - return secret_item_get_type(); - } - - /** - * Finish asynchronous operation to get a new item proxy for a secret - * item in the secret service. - * - * Params: - * result = the asynchronous result passed to the callback - * - * Returns: the new item, which should be unreferenced - * with [method@GObject.Object.unref] - * - * Throws: GException on failure. - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(AsyncResultIF result) - { - GError* err = null; - - auto __p = secret_item_new_for_dbus_path_finish((result is null) ? null : result.getAsyncResultStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } - - if(__p is null) - { - throw new ConstructionException("null returned by new_for_dbus_path_finish"); - } - - this(cast(SecretItem*) __p, true); - } - - /** - * Get a new item proxy for a secret item in the secret service. - * - * If @service is %NULL, then [func@Service.get_sync] will be called to get - * the default [class@Service] proxy. - * - * This method may block indefinitely and should not be used in user interface - * threads. - * - * Params: - * service = a secret service object - * itemPath = the D-Bus path of the item - * flags = initialization flags for the new item - * cancellable = optional cancellation object - * - * Returns: the new item, which should be unreferenced - * with [method@GObject.Object.unref] - * - * Throws: GException on failure. - * Throws: ConstructionException GTK+ fails to create the object. - */ - public this(Service service, string itemPath, SecretItemFlags flags, Cancellable cancellable) - { - GError* err = null; - - auto __p = secret_item_new_for_dbus_path_sync((service is null) ? null : service.getServiceStruct(), Str.toStringz(itemPath), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } + /** the main Gtk struct */ + protected SecretItem* secretItem; + + /** Get the main Gtk struct */ + public SecretItem* getItemStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretItem; + } + + /** the main Gtk struct as a void* */ + protected override void* getStruct() + { + return cast(void*)secretItem; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretItem* secretItem, bool ownedRef = false) + { + this.secretItem = secretItem; + super(cast(GDBusProxy*)secretItem, ownedRef); + } + + // add the Retrievable capabilities + mixin RetrievableT!(SecretItem); + + + /** */ + public static GType getType() + { + return secret_item_get_type(); + } + + /** + * Finish asynchronous operation to get a new item proxy for a secret + * item in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: the new item, which should be unreferenced + * with [method@GObject.Object.unref] + * + * Throws: GException on failure. + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_item_new_for_dbus_path_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + throw new ConstructionException("null returned by new_for_dbus_path_finish"); + } + + this(cast(SecretItem*) __p, true); + } + + /** + * Get a new item proxy for a secret item in the secret service. + * + * If @service is %NULL, then [func@Service.get_sync] will be called to get + * the default [class@Service] proxy. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * service = a secret service object + * itemPath = the D-Bus path of the item + * flags = initialization flags for the new item + * cancellable = optional cancellation object + * + * Returns: the new item, which should be unreferenced + * with [method@GObject.Object.unref] + * + * Throws: GException on failure. + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(Service service, string itemPath, SecretItemFlags flags, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_item_new_for_dbus_path_sync((service is null) ? null : service.getServiceStruct(), Str.toStringz(itemPath), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } if(__p is null) { @@ -467,8 +467,6 @@ public class Item : DBusProxy, RetrievableIF return secret_item_get_created(secretItem); } - alias getFlags = DBusProxy.getFlags; - /** * Get the flags representing what features of the #SecretItem proxy * have been initialized. @@ -482,6 +480,7 @@ public class Item : DBusProxy, RetrievableIF { return secret_item_get_flags(secretItem); } + alias getFlags = DBusProxy.getFlags; /** * Get the label of this item. diff --git a/keyring/libsecret/secret/Password.d b/keyring/libsecret/secret/Password.d new file mode 100644 index 0000000..7b2682c --- /dev/null +++ b/keyring/libsecret/secret/Password.d @@ -0,0 +1,598 @@ +module secret.Password; + +private import gio.AsyncResultIF; +private import gio.Cancellable; +private import glib.ErrorG; +private import glib.GException; +private import glib.HashTable; +private import glib.ListG; +private import glib.Str; +private import glib.c.functions; +private import gobject.ObjectG; +private import secret.Schema; +private import secret.Value; +private import secret.c.functions; +public import secret.c.types; + + +/** */ +public struct Password +{ + + /** + * Finish an asynchronous operation to remove passwords from the secret + * service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: whether any passwords were removed + * + * Throws: GException on failure. + */ + public static bool clearFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_password_clear_finish((result is null) ? null : result.getAsyncResultStruct(), &err) != 0; + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + return __p; + } + + /** + * Remove unlocked matching passwords from the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * All unlocked items that match the attributes will be deleted. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * schema = the schema for the attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + */ + public static void clearv(Schema schema, HashTable attributes, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_password_clearv((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Remove unlocked matching passwords from the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * All unlocked items that match the attributes will be deleted. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for the attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * + * Returns: whether any passwords were removed + * + * Throws: GException on failure. + */ + public static bool clearvSync(Schema schema, HashTable attributes, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_password_clearv_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err) != 0; + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + return __p; + } + + /** + * Clear the memory used by a password, and then free it. + * + * This function must be used to free nonpageable memory returned by + * [func@password_lookup_nonpageable_finish], + * [func@password_lookup_nonpageable_sync] or + * [func@password_lookupv_nonpageable_sync]. + * + * Params: + * password = password to free + */ + public static void free(string password) + { + secret_password_free(Str.toStringz(password)); + } + + /** + * Finish an asynchronous operation to lookup a password in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a newly allocated [struct@Value], which should be + * released with [method@Value.unref], or %NULL if no secret found + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static Value lookupBinaryFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_password_lookup_binary_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Value)(cast(SecretValue*) __p, true); + } + + /** + * Finish an asynchronous operation to lookup a password in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a new password string which should be freed with + * [func@password_free] or may be freed with [func@GLib.free] when done + * + * Throws: GException on failure. + */ + public static string lookupFinish(AsyncResultIF result) + { + GError* err = null; + + auto retStr = secret_password_lookup_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } + + /** + * Finish an asynchronous operation to lookup a password in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a new password string stored in nonpageable memory + * which must be freed with [func@password_free] when done + * + * Throws: GException on failure. + */ + public static string lookupNonpageableFinish(AsyncResultIF result) + { + GError* err = null; + + auto retStr = secret_password_lookup_nonpageable_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } + + /** + * Lookup a password in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If no secret is found then %NULL is returned. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + */ + public static void lookupv(Schema schema, HashTable attributes, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_password_lookupv((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Lookup a password in the secret service. + * + * This is similar to [func@password_lookupv_sync], but returns a + * [struct@Value] instead of a null-terminated password. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * + * Returns: a newly allocated [struct@Value], which should be + * released with [method@Value.unref], or %NULL if no secret found + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static Value lookupvBinarySync(Schema schema, HashTable attributes, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_password_lookupv_binary_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Value)(cast(SecretValue*) __p, true); + } + + /** + * Lookup a password in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If no secret is found then %NULL is returned. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * + * Returns: a new password string stored in non pageable memory + * which should be freed with [func@password_free] when done + * + * Throws: GException on failure. + */ + public static string lookupvNonpageableSync(Schema schema, HashTable attributes, Cancellable cancellable) + { + GError* err = null; + + auto retStr = secret_password_lookupv_nonpageable_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } + + /** + * Lookup a password in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If no secret is found then %NULL is returned. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * cancellable = optional cancellation object + * + * Returns: a new password string which should be freed with + * [func@password_free] or may be freed with [func@GLib.free] when done + * + * Throws: GException on failure. + */ + public static string lookupvSync(Schema schema, HashTable attributes, Cancellable cancellable) + { + GError* err = null; + + auto retStr = secret_password_lookupv_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } + + /** + * Finish an asynchronous operation to search for items in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a list of + * [iface@Retrievable] containing attributes of the matched items + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static ListG searchFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_password_search_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return new ListG(cast(GList*) __p, true); + } + + /** + * Search for items in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * flags = search option flags + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + * + * Since: 0.19.0 + */ + public static void searchv(Schema schema, HashTable attributes, SecretSearchFlags flags, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_password_searchv((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Search for items in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If no secret is found then %NULL is returned. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * flags = search option flags + * cancellable = optional cancellation object + * + * Returns: a list of + * [iface@Retrievable] containing attributes of the matched items + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static ListG searchvSync(Schema schema, HashTable attributes, SecretSearchFlags flags, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_password_searchv_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return new ListG(cast(GList*) __p, true); + } + + /** + * Finish asynchronous operation to store a password in the secret service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: whether the storage was successful or not + * + * Throws: GException on failure. + */ + public static bool storeFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_password_store_finish((result is null) ? null : result.getAsyncResultStruct(), &err) != 0; + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + return __p; + } + + /** + * Store a password in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If the attributes match a secret item already stored in the collection, then + * the item will be updated with these new values. + * + * If @collection is %NULL, then the default collection will be + * used. Use [const@COLLECTION_SESSION] to store the password in the session + * collection, which doesn't get stored across login sessions. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * collection = a collection alias, or D-Bus object path of the + * collection where to store the secret + * label = label for the secret + * password = the null-terminated password to store + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + */ + public static void storev(Schema schema, HashTable attributes, string collection, string label, string password, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_password_storev((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), Str.toStringz(collection), Str.toStringz(label), Str.toStringz(password), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Store a password in the secret service. + * + * This is similar to [func@password_storev], but takes a + * [struct@Value] as the argument instead of a null-terminated password. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * collection = a collection alias, or D-Bus object path of the + * collection where to store the secret + * label = label for the secret + * value = a [struct@Value] + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + * + * Since: 0.19.0 + */ + public static void storevBinary(Schema schema, HashTable attributes, string collection, string label, Value value, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_password_storev_binary((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), Str.toStringz(collection), Str.toStringz(label), (value is null) ? null : value.getValueStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Store a password in the secret service. + * + * This is similar to [func@password_storev_sync], but takes a [struct@Value] as + * the argument instead of a null-terminated passwords. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * collection = a collection alias, or D-Bus object path of the + * collection where to store the secret + * label = label for the secret + * value = a [struct@Value] + * cancellable = optional cancellation object + * + * Returns: whether the storage was successful or not + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public static bool storevBinarySync(Schema schema, HashTable attributes, string collection, string label, Value value, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_password_storev_binary_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), Str.toStringz(collection), Str.toStringz(label), (value is null) ? null : value.getValueStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err) != 0; + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + return __p; + } + + /** + * Store a password in the secret service. + * + * The @attributes should be a set of key and value string pairs. + * + * If the attributes match a secret item already stored in the collection, then + * the item will be updated with these new values. + * + * If @collection is %NULL, then the default collection will be + * used. Use [const@COLLECTION_SESSION] to store the password in the session + * collection, which doesn't get stored across login sessions. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * schema = the schema for attributes + * attributes = the attribute keys and values + * collection = a collection alias, or D-Bus object path of the + * collection where to store the secret + * label = label for the secret + * password = the null-terminated password to store + * cancellable = optional cancellation object + * + * Returns: whether the storage was successful or not + * + * Throws: GException on failure. + */ + public static bool storevSync(Schema schema, HashTable attributes, string collection, string label, string password, Cancellable cancellable) + { + GError* err = null; + + auto __p = secret_password_storev_sync((schema is null) ? null : schema.getSchemaStruct(), (attributes is null) ? null : attributes.getHashTableStruct(), Str.toStringz(collection), Str.toStringz(label), Str.toStringz(password), (cancellable is null) ? null : cancellable.getCancellableStruct(), &err) != 0; + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + return __p; + } + + /** + * Clear the memory used by a password. + * + * Params: + * password = password to clear + */ + public static void wipe(string password) + { + secret_password_wipe(Str.toStringz(password)); + } +} diff --git a/frontends/common/linux/secret/Prompt.d b/keyring/libsecret/secret/Prompt.d similarity index 58% rename from frontends/common/linux/secret/Prompt.d rename to keyring/libsecret/secret/Prompt.d index b5739c9..26ac9be 100644 --- a/frontends/common/linux/secret/Prompt.d +++ b/keyring/libsecret/secret/Prompt.d @@ -36,100 +36,100 @@ public import secret.c.types; */ public class Prompt : DBusProxy { - /** the main Gtk struct */ - protected SecretPrompt* secretPrompt; - - /** Get the main Gtk struct */ - public SecretPrompt* getPromptStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretPrompt; - } - - /** the main Gtk struct as a void* */ - protected override void* getStruct() - { - return cast(void*)secretPrompt; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretPrompt* secretPrompt, bool ownedRef = false) - { - this.secretPrompt = secretPrompt; - super(cast(GDBusProxy*)secretPrompt, ownedRef); - } - - - /** */ - public static GType getType() - { - return secret_prompt_get_type(); - } - - /** - * Runs a prompt and performs the prompting. - * - * Returns %TRUE if the prompt was completed and not dismissed. - * - * If @window_id is non-null then it is used as an XWindow id on Linux. The API - * expects this id to be converted to a string using the `%d` printf format. The - * Secret Service can make its prompt transient for the window with this id. In - * some Secret Service implementations this is not possible, so the behavior - * depending on this should degrade gracefully. - * - * This method will return immediately and complete asynchronously. - * - * Params: - * windowId = string form of XWindow id for parent window to be transient for - * returnType = the variant type of the prompt result - * cancellable = optional cancellation object - * callback = called when the operation completes - * userData = data to be passed to the callback - */ - public void perform(string windowId, VariantType returnType, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) - { - secret_prompt_perform(secretPrompt, Str.toStringz(windowId), (returnType is null) ? null : returnType.getVariantTypeStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); - } - - /** - * Complete asynchronous operation to run a prompt and perform the prompting. - * - * Returns a variant result if the prompt was completed and not dismissed. The - * type of result depends on the action the prompt is completing, and is - * defined in the Secret Service DBus API specification. - * - * Params: - * result = the asynchronous result passed to the callback - * - * Returns: %NULL if the prompt was dismissed or an error occurred, - * a variant result if the prompt was successful - * - * Throws: GException on failure. - */ - public Variant performFinish(AsyncResultIF result) - { - GError* err = null; - - auto __p = secret_prompt_perform_finish(secretPrompt, (result is null) ? null : result.getAsyncResultStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } - - if(__p is null) - { - return null; - } - - return new Variant(cast(GVariant*) __p, true); - } - - /** - * Runs a prompt and performs the prompting. + /** the main Gtk struct */ + protected SecretPrompt* secretPrompt; + + /** Get the main Gtk struct */ + public SecretPrompt* getPromptStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretPrompt; + } + + /** the main Gtk struct as a void* */ + protected override void* getStruct() + { + return cast(void*)secretPrompt; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretPrompt* secretPrompt, bool ownedRef = false) + { + this.secretPrompt = secretPrompt; + super(cast(GDBusProxy*)secretPrompt, ownedRef); + } + + + /** */ + public static GType getType() + { + return secret_prompt_get_type(); + } + + /** + * Runs a prompt and performs the prompting. + * + * Returns %TRUE if the prompt was completed and not dismissed. + * + * If @window_id is non-null then it is used as an XWindow id on Linux. The API + * expects this id to be converted to a string using the `%d` printf format. The + * Secret Service can make its prompt transient for the window with this id. In + * some Secret Service implementations this is not possible, so the behavior + * depending on this should degrade gracefully. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * windowId = string form of XWindow id for parent window to be transient for + * returnType = the variant type of the prompt result + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + */ + public void perform(string windowId, VariantType returnType, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_prompt_perform(secretPrompt, Str.toStringz(windowId), (returnType is null) ? null : returnType.getVariantTypeStruct(), (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Complete asynchronous operation to run a prompt and perform the prompting. + * + * Returns a variant result if the prompt was completed and not dismissed. The + * type of result depends on the action the prompt is completing, and is + * defined in the Secret Service DBus API specification. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: %NULL if the prompt was dismissed or an error occurred, + * a variant result if the prompt was successful + * + * Throws: GException on failure. + */ + public Variant performFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_prompt_perform_finish(secretPrompt, (result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return new Variant(cast(GVariant*) __p, true); + } + + /** + * Runs a prompt and performs the prompting. * * Returns a variant result if the prompt was completed and not dismissed. The * type of result depends on the action the prompt is completing, and is defined diff --git a/keyring/libsecret/secret/RetrievableIF.d b/keyring/libsecret/secret/RetrievableIF.d new file mode 100644 index 0000000..861bd9c --- /dev/null +++ b/keyring/libsecret/secret/RetrievableIF.d @@ -0,0 +1,144 @@ +module secret.RetrievableIF; + +private import gio.AsyncResultIF; +private import gio.Cancellable; +private import glib.ErrorG; +private import glib.GException; +private import glib.HashTable; +private import glib.Str; +private import glib.c.functions; +private import gobject.ObjectG; +private import secret.Value; +private import secret.c.functions; +public import secret.c.types; + + +/** + * A read-only view of a secret item in the Secret Service. + * + * #SecretRetrievable provides a read-only view of a secret item + * stored in the Secret Service. + * + * Each item has a value, represented by a [struct@Value], which can be + * retrieved by [method@Retrievable.retrieve_secret] and + * [method@Retrievable.retrieve_secret_finish]. + * + * Since: 0.19.0 + */ +public interface RetrievableIF{ + /** Get the main Gtk struct */ + public SecretRetrievable* getRetrievableStruct(bool transferOwnership = false); + + /** the main Gtk struct as a void* */ + protected void* getStruct(); + + + /** */ + public static GType getType() + { + return secret_retrievable_get_type(); + } + + /** + * Get the attributes of this object. + * + * The attributes are a mapping of string keys to string values. + * Attributes are used to search for items. Attributes are not stored + * or transferred securely by the secret service. + * + * Do not modify the attribute returned by this method. + * + * Returns: a new reference + * to the attributes, which should not be modified, and + * released with [func@GLib.HashTable.unref] + * + * Since: 0.19.0 + */ + public HashTable getAttributes(); + + /** + * Get the created date and time of the object. + * + * The return value is the number of seconds since the unix epoch, January 1st + * 1970. + * + * Returns: the created date and time + * + * Since: 0.19.0 + */ + public ulong getCreated(); + + /** + * Get the label of this item. + * + * Returns: the label, which should be freed with [func@GLib.free] + * + * Since: 0.19.0 + */ + public string getLabel(); + + /** + * Get the modified date and time of the object. + * + * The return value is the number of seconds since the unix epoch, January 1st + * 1970. + * + * Returns: the modified date and time + * + * Since: 0.19.0 + */ + public ulong getModified(); + + /** + * Retrieve the secret value of this object. + * + * Each retrievable object has a single secret which might be a + * password or some other secret binary value. + * + * This function returns immediately and completes asynchronously. + * + * Params: + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to pass to the callback + * + * Since: 0.19.0 + */ + public void retrieveSecret(Cancellable cancellable, GAsyncReadyCallback callback, void* userData); + + /** + * Complete asynchronous operation to retrieve the secret value of this object. + * + * Params: + * result = asynchronous result passed to callback + * + * Returns: the secret value which should be + * released with [method@Value.unref], or %NULL + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public Value retrieveSecretFinish(AsyncResultIF result); + + /** + * Retrieve the secret value of this object synchronously. + * + * Each retrievable object has a single secret which might be a + * password or some other secret binary value. + * + * This method may block indefinitely and should not be used in user interface + * threads. + * + * Params: + * cancellable = optional cancellation object + * + * Returns: the secret value which should be + * released with [method@Value.unref], or %NULL + * + * Since: 0.19.0 + * + * Throws: GException on failure. + */ + public Value retrieveSecretSync(Cancellable cancellable); +} diff --git a/frontends/common/linux/secret/RetrievableT.d b/keyring/libsecret/secret/RetrievableT.d similarity index 51% rename from frontends/common/linux/secret/RetrievableT.d rename to keyring/libsecret/secret/RetrievableT.d index c7842ba..1f5c758 100644 --- a/frontends/common/linux/secret/RetrievableT.d +++ b/keyring/libsecret/secret/RetrievableT.d @@ -27,101 +27,101 @@ public import secret.c.types; */ public template RetrievableT(TStruct) { - /** Get the main Gtk struct */ - public SecretRetrievable* getRetrievableStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return cast(SecretRetrievable*)getStruct(); - } - - - /** - * Get the attributes of this object. - * - * The attributes are a mapping of string keys to string values. - * Attributes are used to search for items. Attributes are not stored - * or transferred securely by the secret service. - * - * Do not modify the attribute returned by this method. - * - * Returns: a new reference - * to the attributes, which should not be modified, and - * released with [func@GLib.HashTable.unref] - * - * Since: 0.19.0 - */ - public HashTable getAttributes() - { - auto __p = secret_retrievable_get_attributes(getRetrievableStruct()); - - if(__p is null) - { - return null; - } - - return new HashTable(cast(GHashTable*) __p, true); - } - - /** - * Get the created date and time of the object. - * - * The return value is the number of seconds since the unix epoch, January 1st - * 1970. - * - * Returns: the created date and time - * - * Since: 0.19.0 - */ - public ulong getCreated() - { - return secret_retrievable_get_created(getRetrievableStruct()); - } - - /** - * Get the label of this item. - * - * Returns: the label, which should be freed with [func@GLib.free] - * - * Since: 0.19.0 - */ - public string getLabel() - { - auto retStr = secret_retrievable_get_label(getRetrievableStruct()); - - scope(exit) Str.freeString(retStr); - return Str.toString(retStr); - } - - /** - * Get the modified date and time of the object. - * - * The return value is the number of seconds since the unix epoch, January 1st - * 1970. - * - * Returns: the modified date and time - * - * Since: 0.19.0 - */ - public ulong getModified() - { - return secret_retrievable_get_modified(getRetrievableStruct()); - } - - /** - * Retrieve the secret value of this object. - * - * Each retrievable object has a single secret which might be a - * password or some other secret binary value. - * - * This function returns immediately and completes asynchronously. - * - * Params: - * cancellable = optional cancellation object - * callback = called when the operation completes - * userData = data to pass to the callback - * - * Since: 0.19.0 + /** Get the main Gtk struct */ + public SecretRetrievable* getRetrievableStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return cast(SecretRetrievable*)getStruct(); + } + + + /** + * Get the attributes of this object. + * + * The attributes are a mapping of string keys to string values. + * Attributes are used to search for items. Attributes are not stored + * or transferred securely by the secret service. + * + * Do not modify the attribute returned by this method. + * + * Returns: a new reference + * to the attributes, which should not be modified, and + * released with [func@GLib.HashTable.unref] + * + * Since: 0.19.0 + */ + public HashTable getAttributes() + { + auto __p = secret_retrievable_get_attributes(getRetrievableStruct()); + + if(__p is null) + { + return null; + } + + return new HashTable(cast(GHashTable*) __p, true); + } + + /** + * Get the created date and time of the object. + * + * The return value is the number of seconds since the unix epoch, January 1st + * 1970. + * + * Returns: the created date and time + * + * Since: 0.19.0 + */ + public ulong getCreated() + { + return secret_retrievable_get_created(getRetrievableStruct()); + } + + /** + * Get the label of this item. + * + * Returns: the label, which should be freed with [func@GLib.free] + * + * Since: 0.19.0 + */ + public string getLabel() + { + auto retStr = secret_retrievable_get_label(getRetrievableStruct()); + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } + + /** + * Get the modified date and time of the object. + * + * The return value is the number of seconds since the unix epoch, January 1st + * 1970. + * + * Returns: the modified date and time + * + * Since: 0.19.0 + */ + public ulong getModified() + { + return secret_retrievable_get_modified(getRetrievableStruct()); + } + + /** + * Retrieve the secret value of this object. + * + * Each retrievable object has a single secret which might be a + * password or some other secret binary value. + * + * This function returns immediately and completes asynchronously. + * + * Params: + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to pass to the callback + * + * Since: 0.19.0 */ public void retrieveSecret(Cancellable cancellable, GAsyncReadyCallback callback, void* userData) { diff --git a/keyring/libsecret/secret/Schema.d b/keyring/libsecret/secret/Schema.d new file mode 100644 index 0000000..64844e2 --- /dev/null +++ b/keyring/libsecret/secret/Schema.d @@ -0,0 +1,215 @@ +module secret.Schema; + +private import glib.ConstructionException; +private import glib.HashTable; +private import glib.Str; +private import gobject.ObjectG; +private import linker.Loader; +private import secret.c.functions; +public import secret.c.types; + + +/** + * Represents a set of attributes that are stored with an item. + * + * These schemas are used for interoperability between various services storing + * the same types of items. + * + * Each schema has a name like `org.gnome.keyring.NetworkPassword`, and defines a + * set of attributes, and types (string, integer, boolean) for those attributes. + * + * Attributes are stored as strings in the Secret Service, and the attribute types + * simply define standard ways to store integer and boolean values as strings. + * Attributes are represented in libsecret via a [struct@GLib.HashTable] with + * string keys and values. Even for values that defined as an integer or boolean in + * the schema, the attribute values in the [struct@GLib.HashTable] are strings. + * Boolean values are stored as the strings 'true' and 'false'. Integer values are + * stored in decimal, with a preceding negative sign for negative integers. + * + * Schemas are handled entirely on the client side by this library. The name of the + * schema is automatically stored as an attribute on the item. + * + * Normally when looking up passwords only those with matching schema names are + * returned. If the schema @flags contain the `SECRET_SCHEMA_DONT_MATCH_NAME` flag, + * then lookups will not check that the schema name matches that on the item, only + * the schema's attributes are matched. This is useful when you are looking up + * items that are not stored by the libsecret library. Other libraries such as + * libgnome-keyring don't store the schema name. + * + * Additional schemas can be defined via the %SecretSchema structure like this: + * + * ```c + * // in a header: + * + * const SecretSchema * example_get_schema (void) G_GNUC_CONST; + * + * #define EXAMPLE_SCHEMA example_get_schema () + * + * + * // in a .c file + * + * const SecretSchema * + * example_get_schema (void) + * { + * static const SecretSchema the_schema = { + * "org.example.Password", SECRET_SCHEMA_NONE, + * { + * { "number", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, + * { "string", SECRET_SCHEMA_ATTRIBUTE_STRING }, + * { "even", SECRET_SCHEMA_ATTRIBUTE_BOOLEAN }, + * { NULL, 0 }, + * } + * }; + * return &the_schema; + * } + * ``` + */ +public class Schema +{ + /** the main Gtk struct */ + protected SecretSchema* secretSchema; + protected bool ownedRef; + + /** Get the main Gtk struct */ + public SecretSchema* getSchemaStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretSchema; + } + + /** the main Gtk struct as a void* */ + protected void* getStruct() + { + return cast(void*)secretSchema; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretSchema* secretSchema, bool ownedRef = false) + { + this.secretSchema = secretSchema; + this.ownedRef = ownedRef; + } + + ~this () + { + if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) + secret_schema_unref(secretSchema); + } + + + /** */ + public static GType getType() + { + return secret_schema_get_type(); + } + + /** + * Using this function is not normally necessary from C code. This is useful + * for constructing #SecretSchema structures in bindings. + * + * A schema represents a set of attributes that are stored with an item. These + * schemas are used for interoperability between various services storing the + * same types of items. + * + * Each schema has an @name like `org.gnome.keyring.NetworkPassword`, and + * defines a set of attributes names, and types (string, integer, boolean) for + * those attributes. + * + * Each key in the @attributes table should be a attribute name strings, and + * the values in the table should be integers from the [enum@SchemaAttributeType] + * enumeration, representing the attribute type for each attribute name. + * + * Normally when looking up passwords only those with matching schema names are + * returned. If the schema @flags contain the %SECRET_SCHEMA_DONT_MATCH_NAME flag, + * then lookups will not check that the schema name matches that on the item, only + * the schema's attributes are matched. This is useful when you are looking up items + * that are not stored by the libsecret library. Other libraries such as libgnome-keyring + * don't store the schema name. + * + * Params: + * name = the dotted name of the schema + * flags = the flags for the schema + * attributeNamesAndTypes = the attribute names and types of those attributes + * + * Returns: the new schema, which should be unreferenced with + * [method@Schema.unref] when done + * + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(string name, SecretSchemaFlags flags, HashTable attributeNamesAndTypes) + { + auto __p = secret_schema_newv(Str.toStringz(name), flags, (attributeNamesAndTypes is null) ? null : attributeNamesAndTypes.getHashTableStruct()); + + if(__p is null) + { + throw new ConstructionException("null returned by newv"); + } + + this(cast(SecretSchema*) __p); + } + + alias doref = ref_; + /** + * Adds a reference to the #SecretSchema. + * + * It is not normally necessary to call this function from C code, and is + * mainly present for the sake of bindings. If the @schema was statically + * allocated, then this function will copy the schema. + * + * Returns: the referenced schema, which should be later + * unreferenced with [method@Schema.unref] + */ + public Schema ref_() + { + auto __p = secret_schema_ref(secretSchema); + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Schema)(cast(SecretSchema*) __p, true); + } + + /** + * Releases a reference to the #SecretSchema. + * + * If the last reference is released then the schema will be freed. + * + * It is not normally necessary to call this function from C code, and is + * mainly present for the sake of bindings. It is an error to call this for + * a @schema that was statically allocated. + */ + public void unref() + { + secret_schema_unref(secretSchema); + } + + /** + * Get a secret storage schema of the given @type. + * + * C code may access the schemas (such as %SECRET_SCHEMA_NOTE) directly, but + * language bindings cannot, and must use this accessor. + * + * Params: + * type = type of schema to get + * + * Returns: schema type + * + * Since: 0.18.6 + */ + public static Schema getSchema(SecretSchemaType type) + { + auto __p = secret_get_schema(type); + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Schema)(cast(SecretSchema*) __p); + } +} diff --git a/keyring/libsecret/secret/SchemaAttribute.d b/keyring/libsecret/secret/SchemaAttribute.d new file mode 100644 index 0000000..838d2e8 --- /dev/null +++ b/keyring/libsecret/secret/SchemaAttribute.d @@ -0,0 +1,83 @@ +module secret.SchemaAttribute; + +private import glib.MemorySlice; +private import glib.Str; +private import glib.c.functions; +private import linker.Loader; +private import secret.c.functions; +public import secret.c.types; + + +/** + * An attribute in a #SecretSchema. + */ +public final class SchemaAttribute +{ + /** the main Gtk struct */ + protected SecretSchemaAttribute* secretSchemaAttribute; + protected bool ownedRef; + + /** Get the main Gtk struct */ + public SecretSchemaAttribute* getSchemaAttributeStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretSchemaAttribute; + } + + /** the main Gtk struct as a void* */ + protected void* getStruct() + { + return cast(void*)secretSchemaAttribute; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretSchemaAttribute* secretSchemaAttribute, bool ownedRef = false) + { + this.secretSchemaAttribute = secretSchemaAttribute; + this.ownedRef = ownedRef; + } + + ~this () + { + if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) + sliceFree(secretSchemaAttribute); + } + + + /** + * name of the attribute + */ + public @property string name() + { + return Str.toString(secretSchemaAttribute.name); + } + + /** Ditto */ + public @property void name(string value) + { + secretSchemaAttribute.name = Str.toStringz(value); + } + + /** + * the type of the attribute + */ + public @property SecretSchemaAttributeType type() + { + return secretSchemaAttribute.type; + } + + /** Ditto */ + public @property void type(SecretSchemaAttributeType value) + { + secretSchemaAttribute.type = value; + } + + /** */ + public static GType getType() + { + return secret_schema_attribute_get_type(); + } +} diff --git a/frontends/common/linux/secret/Service.d b/keyring/libsecret/secret/Service.d similarity index 96% rename from frontends/common/linux/secret/Service.d rename to keyring/libsecret/secret/Service.d index 58ffb36..80a4654 100644 --- a/frontends/common/linux/secret/Service.d +++ b/keyring/libsecret/secret/Service.d @@ -68,111 +68,111 @@ public import secret.c.types; */ public class Service : DBusProxy, BackendIF { - /** the main Gtk struct */ - protected SecretService* secretService; - - /** Get the main Gtk struct */ - public SecretService* getServiceStruct(bool transferOwnership = false) - { - if (transferOwnership) - ownedRef = false; - return secretService; - } - - /** the main Gtk struct as a void* */ - protected override void* getStruct() - { - return cast(void*)secretService; - } - - /** - * Sets our main struct and passes it to the parent class. - */ - public this (SecretService* secretService, bool ownedRef = false) - { - this.secretService = secretService; - super(cast(GDBusProxy*)secretService, ownedRef); - } - - // add the Backend capabilities - mixin BackendT!(SecretService); - - - /** */ - public static GType getType() - { - return secret_service_get_type(); - } - - /** - * Disconnect the default #SecretService proxy returned by [func@Service.get] - * and [func@Service.get_sync]. - * - * It is not necessary to call this function, but you may choose to do so at - * program exit. It is useful for testing that memory is not leaked. - * - * This function is safe to call at any time. But if other objects in this - * library are still referenced, then this will not result in all memory - * being freed. - */ - public static void disconnect() - { - secret_service_disconnect(); - } - - /** - * Get a #SecretService proxy for the Secret Service. - * - * If such a proxy object already exists, then the same proxy is returned. - * - * If @flags contains any flags of which parts of the secret service to - * ensure are initialized, then those will be initialized before completing. - * - * This method will return immediately and complete asynchronously. - * - * Params: - * flags = flags for which service functionality to ensure is initialized - * cancellable = optional cancellation object - * callback = called when the operation completes - * userData = data to be passed to the callback - */ - public static void get(SecretServiceFlags flags, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) - { - secret_service_get(flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); - } - - /** - * Complete an asynchronous operation to get a #SecretService proxy for the - * Secret Service. - * - * Params: - * result = the asynchronous result passed to the callback - * - * Returns: a new reference to a #SecretService proxy, which - * should be released with [method@GObject.Object.unref]. - * - * Throws: GException on failure. - */ - public static Service getFinish(AsyncResultIF result) - { - GError* err = null; - - auto __p = secret_service_get_finish((result is null) ? null : result.getAsyncResultStruct(), &err); - - if (err !is null) - { - throw new GException( new ErrorG(err) ); - } - - if(__p is null) - { - return null; - } - - return ObjectG.getDObject!(Service)(cast(SecretService*) __p, true); - } - - /** + /** the main Gtk struct */ + protected SecretService* secretService; + + /** Get the main Gtk struct */ + public SecretService* getServiceStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretService; + } + + /** the main Gtk struct as a void* */ + protected override void* getStruct() + { + return cast(void*)secretService; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretService* secretService, bool ownedRef = false) + { + this.secretService = secretService; + super(cast(GDBusProxy*)secretService, ownedRef); + } + + // add the Backend capabilities + mixin BackendT!(SecretService); + + + /** */ + public static GType getType() + { + return secret_service_get_type(); + } + + /** + * Disconnect the default #SecretService proxy returned by [func@Service.get] + * and [func@Service.get_sync]. + * + * It is not necessary to call this function, but you may choose to do so at + * program exit. It is useful for testing that memory is not leaked. + * + * This function is safe to call at any time. But if other objects in this + * library are still referenced, then this will not result in all memory + * being freed. + */ + public static void disconnect() + { + secret_service_disconnect(); + } + + /** + * Get a #SecretService proxy for the Secret Service. + * + * If such a proxy object already exists, then the same proxy is returned. + * + * If @flags contains any flags of which parts of the secret service to + * ensure are initialized, then those will be initialized before completing. + * + * This method will return immediately and complete asynchronously. + * + * Params: + * flags = flags for which service functionality to ensure is initialized + * cancellable = optional cancellation object + * callback = called when the operation completes + * userData = data to be passed to the callback + */ + public static void get(SecretServiceFlags flags, Cancellable cancellable, GAsyncReadyCallback callback, void* userData) + { + secret_service_get(flags, (cancellable is null) ? null : cancellable.getCancellableStruct(), callback, userData); + } + + /** + * Complete an asynchronous operation to get a #SecretService proxy for the + * Secret Service. + * + * Params: + * result = the asynchronous result passed to the callback + * + * Returns: a new reference to a #SecretService proxy, which + * should be released with [method@GObject.Object.unref]. + * + * Throws: GException on failure. + */ + public static Service getFinish(AsyncResultIF result) + { + GError* err = null; + + auto __p = secret_service_get_finish((result is null) ? null : result.getAsyncResultStruct(), &err); + + if (err !is null) + { + throw new GException( new ErrorG(err) ); + } + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Service)(cast(SecretService*) __p, true); + } + + /** * Get a #SecretService proxy for the Secret Service. * * If such a proxy object already exists, then the same proxy is returned. @@ -871,8 +871,6 @@ public class Service : DBusProxy, BackendIF return new ListG(cast(GList*) __p, true); } - alias getFlags = DBusProxy.getFlags; - /** * Get the flags representing what features of the #SecretService proxy * have been initialized. @@ -886,6 +884,7 @@ public class Service : DBusProxy, BackendIF { return secret_service_get_flags(secretService); } + alias getFlags = DBusProxy.getFlags; /** * Get the GObject type for items instantiated by this service. diff --git a/keyring/libsecret/secret/Value.d b/keyring/libsecret/secret/Value.d new file mode 100644 index 0000000..1864e3f --- /dev/null +++ b/keyring/libsecret/secret/Value.d @@ -0,0 +1,219 @@ +module secret.Value; + +private import glib.ConstructionException; +private import glib.Str; +private import glib.c.functions; +private import gobject.ObjectG; +private import linker.Loader; +private import secret.c.functions; +public import secret.c.types; + + +/** + * A value containing a secret + * + * A #SecretValue contains a password or other secret value. + * + * Use [method@Value.get] to get the actual secret data, such as a password. + * The secret data is not necessarily null-terminated, unless the content type + * is "text/plain". + * + * Each #SecretValue has a content type. For passwords, this is `text/plain`. + * Use [method@Value.get_content_type] to look at the content type. + * + * #SecretValue is reference counted and immutable. The secret data is only + * freed when all references have been released via [method@Value.unref]. + */ +public class Value +{ + /** the main Gtk struct */ + protected SecretValue* secretValue; + protected bool ownedRef; + + /** Get the main Gtk struct */ + public SecretValue* getValueStruct(bool transferOwnership = false) + { + if (transferOwnership) + ownedRef = false; + return secretValue; + } + + /** the main Gtk struct as a void* */ + protected void* getStruct() + { + return cast(void*)secretValue; + } + + /** + * Sets our main struct and passes it to the parent class. + */ + public this (SecretValue* secretValue, bool ownedRef = false) + { + this.secretValue = secretValue; + this.ownedRef = ownedRef; + } + + ~this () + { + if ( Linker.isLoaded(LIBRARY_SECRET[0]) && ownedRef ) + secret_value_unref(secretValue); + } + + + /** */ + public static GType getType() + { + return secret_value_get_type(); + } + + /** + * Create a #SecretValue for the secret data passed in. + * + * The secret data is copied into non-pageable 'secure' memory. + * + * If the length is less than zero, then @secret is assumed to be + * null-terminated. + * + * Params: + * secret = the secret data + * length = the length of the data + * contentType = the content type of the data + * + * Returns: the new #SecretValue + * + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(string secret, ptrdiff_t length, string contentType) + { + auto __p = secret_value_new(Str.toStringz(secret), length, Str.toStringz(contentType)); + + if(__p is null) + { + throw new ConstructionException("null returned by new"); + } + + this(cast(SecretValue*) __p); + } + + /** + * Create a #SecretValue for the secret data passed in. + * + * The secret data is not copied, and will later be freed with the @destroy + * function. + * + * If the length is less than zero, then @secret is assumed to be + * null-terminated. + * + * Params: + * secret = the secret data + * length = the length of the data + * contentType = the content type of the data + * destroy = function to call to free the secret data + * + * Returns: the new #SecretValue + * + * Throws: ConstructionException GTK+ fails to create the object. + */ + public this(string secret, ptrdiff_t length, string contentType, GDestroyNotify destroy) + { + auto __p = secret_value_new_full(Str.toStringz(secret), length, Str.toStringz(contentType), destroy); + + if(__p is null) + { + throw new ConstructionException("null returned by new_full"); + } + + this(cast(SecretValue*) __p); + } + + /** + * Get the secret data in the #SecretValue. + * + * The value is not necessarily null-terminated unless it was created with + * [ctor@Value.new] or a null-terminated string was passed to + * [ctor@Value.new_full]. + * + * Returns: the secret data + */ + public string get() + { + size_t length; + + return Str.toString(secret_value_get(secretValue, &length)); + } + + /** + * Get the content type of the secret value, such as + * `text/plain`. + * + * Returns: the content type + */ + public string getContentType() + { + return Str.toString(secret_value_get_content_type(secretValue)); + } + + /** + * Get the secret data in the #SecretValue if it contains a textual + * value. + * + * The content type must be `text/plain`. + * + * Returns: the content type + */ + public string getText() + { + return Str.toString(secret_value_get_text(secretValue)); + } + + alias doref = ref_; + /** + * Add another reference to the #SecretValue. + * + * For each reference [method@Value.unref] should be called to unreference the + * value. + * + * Returns: the value + */ + public Value ref_() + { + auto __p = secret_value_ref(secretValue); + + if(__p is null) + { + return null; + } + + return ObjectG.getDObject!(Value)(cast(SecretValue*) __p, true); + } + + /** + * Unreference a #SecretValue. + * + * When the last reference is gone, then the value will be freed. + */ + public void unref() + { + secret_value_unref(secretValue); + } + + /** + * Unreference a #SecretValue and steal the secret data in + * #SecretValue as nonpageable memory. + * + * Params: + * length = the length of the secret + * + * Returns: a new password string stored in nonpageable memory + * which must be freed with [func@password_free] when done + * + * Since: 0.19.0 + */ + public string unrefToPassword(ref size_t length) + { + auto retStr = secret_value_unref_to_password(secretValue, &length); + + scope(exit) Str.freeString(retStr); + return Str.toString(retStr); + } +} diff --git a/frontends/common/linux/secret/c/functions.d b/keyring/libsecret/secret/c/functions.d similarity index 68% rename from frontends/common/linux/secret/c/functions.d rename to keyring/libsecret/secret/c/functions.d index 61b143c..6733d75 100644 --- a/frontends/common/linux/secret/c/functions.d +++ b/keyring/libsecret/secret/c/functions.d @@ -5,139 +5,140 @@ import secret.c.types; import linker.Loader; version (Windows) - static immutable LIBRARY_SECRET = ["libsecret-1-0.dll;secret-1-1.0.dll;secret-1.dll"]; + static immutable LIBRARY_SECRET = ["libsecret-1-0.dll;secret-1-1.0.dll;secret-1.dll"]; else version (OSX) - static immutable LIBRARY_SECRET = ["libsecret-1.0.dylib"]; + static immutable LIBRARY_SECRET = ["libsecret-1.0.dylib"]; else - static immutable LIBRARY_SECRET = ["libsecret-1.so.0"]; + static immutable LIBRARY_SECRET = ["libsecret-1.so.0"]; shared static this() { - // secret.Backend - - Linker.link(secret_backend_get_type, "secret_backend_get_type", LIBRARY_SECRET); - Linker.link(secret_backend_get, "secret_backend_get", LIBRARY_SECRET); - Linker.link(secret_backend_get_finish, "secret_backend_get_finish", LIBRARY_SECRET); - - // secret.Collection - - Linker.link(secret_collection_get_type, "secret_collection_get_type", LIBRARY_SECRET); - Linker.link(secret_collection_new_for_dbus_path_finish, "secret_collection_new_for_dbus_path_finish", LIBRARY_SECRET); - Linker.link(secret_collection_new_for_dbus_path_sync, "secret_collection_new_for_dbus_path_sync", LIBRARY_SECRET); - Linker.link(secret_collection_create, "secret_collection_create", LIBRARY_SECRET); - Linker.link(secret_collection_create_finish, "secret_collection_create_finish", LIBRARY_SECRET); - Linker.link(secret_collection_create_sync, "secret_collection_create_sync", LIBRARY_SECRET); - Linker.link(secret_collection_for_alias, "secret_collection_for_alias", LIBRARY_SECRET); - Linker.link(secret_collection_for_alias_finish, "secret_collection_for_alias_finish", LIBRARY_SECRET); - Linker.link(secret_collection_for_alias_sync, "secret_collection_for_alias_sync", LIBRARY_SECRET); - Linker.link(secret_collection_new_for_dbus_path, "secret_collection_new_for_dbus_path", LIBRARY_SECRET); - Linker.link(secret_collection_delete, "secret_collection_delete", LIBRARY_SECRET); - Linker.link(secret_collection_delete_finish, "secret_collection_delete_finish", LIBRARY_SECRET); - Linker.link(secret_collection_delete_sync, "secret_collection_delete_sync", LIBRARY_SECRET); - Linker.link(secret_collection_get_created, "secret_collection_get_created", LIBRARY_SECRET); - Linker.link(secret_collection_get_flags, "secret_collection_get_flags", LIBRARY_SECRET); - Linker.link(secret_collection_get_items, "secret_collection_get_items", LIBRARY_SECRET); - Linker.link(secret_collection_get_label, "secret_collection_get_label", LIBRARY_SECRET); - Linker.link(secret_collection_get_locked, "secret_collection_get_locked", LIBRARY_SECRET); - Linker.link(secret_collection_get_modified, "secret_collection_get_modified", LIBRARY_SECRET); - Linker.link(secret_collection_get_service, "secret_collection_get_service", LIBRARY_SECRET); - Linker.link(secret_collection_load_items, "secret_collection_load_items", LIBRARY_SECRET); - Linker.link(secret_collection_load_items_finish, "secret_collection_load_items_finish", LIBRARY_SECRET); - Linker.link(secret_collection_load_items_sync, "secret_collection_load_items_sync", LIBRARY_SECRET); - Linker.link(secret_collection_refresh, "secret_collection_refresh", LIBRARY_SECRET); - Linker.link(secret_collection_search, "secret_collection_search", LIBRARY_SECRET); - Linker.link(secret_collection_search_finish, "secret_collection_search_finish", LIBRARY_SECRET); - Linker.link(secret_collection_search_for_dbus_paths, "secret_collection_search_for_dbus_paths", LIBRARY_SECRET); - Linker.link(secret_collection_search_for_dbus_paths_finish, "secret_collection_search_for_dbus_paths_finish", LIBRARY_SECRET); - Linker.link(secret_collection_search_for_dbus_paths_sync, "secret_collection_search_for_dbus_paths_sync", LIBRARY_SECRET); - Linker.link(secret_collection_search_sync, "secret_collection_search_sync", LIBRARY_SECRET); - Linker.link(secret_collection_set_label, "secret_collection_set_label", LIBRARY_SECRET); - Linker.link(secret_collection_set_label_finish, "secret_collection_set_label_finish", LIBRARY_SECRET); - Linker.link(secret_collection_set_label_sync, "secret_collection_set_label_sync", LIBRARY_SECRET); - - // secret.Item - - Linker.link(secret_item_get_type, "secret_item_get_type", LIBRARY_SECRET); - Linker.link(secret_item_new_for_dbus_path_finish, "secret_item_new_for_dbus_path_finish", LIBRARY_SECRET); - Linker.link(secret_item_new_for_dbus_path_sync, "secret_item_new_for_dbus_path_sync", LIBRARY_SECRET); - Linker.link(secret_item_create, "secret_item_create", LIBRARY_SECRET); - Linker.link(secret_item_create_finish, "secret_item_create_finish", LIBRARY_SECRET); - Linker.link(secret_item_create_sync, "secret_item_create_sync", LIBRARY_SECRET); - Linker.link(secret_item_load_secrets, "secret_item_load_secrets", LIBRARY_SECRET); - Linker.link(secret_item_load_secrets_finish, "secret_item_load_secrets_finish", LIBRARY_SECRET); - Linker.link(secret_item_load_secrets_sync, "secret_item_load_secrets_sync", LIBRARY_SECRET); - Linker.link(secret_item_new_for_dbus_path, "secret_item_new_for_dbus_path", LIBRARY_SECRET); - Linker.link(secret_item_delete, "secret_item_delete", LIBRARY_SECRET); - Linker.link(secret_item_delete_finish, "secret_item_delete_finish", LIBRARY_SECRET); - Linker.link(secret_item_delete_sync, "secret_item_delete_sync", LIBRARY_SECRET); - Linker.link(secret_item_get_attributes, "secret_item_get_attributes", LIBRARY_SECRET); - Linker.link(secret_item_get_created, "secret_item_get_created", LIBRARY_SECRET); - Linker.link(secret_item_get_flags, "secret_item_get_flags", LIBRARY_SECRET); - Linker.link(secret_item_get_label, "secret_item_get_label", LIBRARY_SECRET); - Linker.link(secret_item_get_locked, "secret_item_get_locked", LIBRARY_SECRET); - Linker.link(secret_item_get_modified, "secret_item_get_modified", LIBRARY_SECRET); - Linker.link(secret_item_get_schema_name, "secret_item_get_schema_name", LIBRARY_SECRET); - Linker.link(secret_item_get_secret, "secret_item_get_secret", LIBRARY_SECRET); - Linker.link(secret_item_get_service, "secret_item_get_service", LIBRARY_SECRET); - Linker.link(secret_item_load_secret, "secret_item_load_secret", LIBRARY_SECRET); - Linker.link(secret_item_load_secret_finish, "secret_item_load_secret_finish", LIBRARY_SECRET); - Linker.link(secret_item_load_secret_sync, "secret_item_load_secret_sync", LIBRARY_SECRET); - Linker.link(secret_item_refresh, "secret_item_refresh", LIBRARY_SECRET); - Linker.link(secret_item_set_attributes, "secret_item_set_attributes", LIBRARY_SECRET); - Linker.link(secret_item_set_attributes_finish, "secret_item_set_attributes_finish", LIBRARY_SECRET); - Linker.link(secret_item_set_attributes_sync, "secret_item_set_attributes_sync", LIBRARY_SECRET); - Linker.link(secret_item_set_label, "secret_item_set_label", LIBRARY_SECRET); - Linker.link(secret_item_set_label_finish, "secret_item_set_label_finish", LIBRARY_SECRET); - Linker.link(secret_item_set_label_sync, "secret_item_set_label_sync", LIBRARY_SECRET); - Linker.link(secret_item_set_secret, "secret_item_set_secret", LIBRARY_SECRET); - Linker.link(secret_item_set_secret_finish, "secret_item_set_secret_finish", LIBRARY_SECRET); - Linker.link(secret_item_set_secret_sync, "secret_item_set_secret_sync", LIBRARY_SECRET); - - // secret.Prompt - - Linker.link(secret_prompt_get_type, "secret_prompt_get_type", LIBRARY_SECRET); - Linker.link(secret_prompt_perform, "secret_prompt_perform", LIBRARY_SECRET); - Linker.link(secret_prompt_perform_finish, "secret_prompt_perform_finish", LIBRARY_SECRET); - Linker.link(secret_prompt_perform_sync, "secret_prompt_perform_sync", LIBRARY_SECRET); - Linker.link(secret_prompt_run, "secret_prompt_run", LIBRARY_SECRET); - - // secret.Retrievable - - Linker.link(secret_retrievable_get_type, "secret_retrievable_get_type", LIBRARY_SECRET); - Linker.link(secret_retrievable_get_attributes, "secret_retrievable_get_attributes", LIBRARY_SECRET); - Linker.link(secret_retrievable_get_created, "secret_retrievable_get_created", LIBRARY_SECRET); - Linker.link(secret_retrievable_get_label, "secret_retrievable_get_label", LIBRARY_SECRET); - Linker.link(secret_retrievable_get_modified, "secret_retrievable_get_modified", LIBRARY_SECRET); - Linker.link(secret_retrievable_retrieve_secret, "secret_retrievable_retrieve_secret", LIBRARY_SECRET); - Linker.link(secret_retrievable_retrieve_secret_finish, "secret_retrievable_retrieve_secret_finish", LIBRARY_SECRET); - Linker.link(secret_retrievable_retrieve_secret_sync, "secret_retrievable_retrieve_secret_sync", LIBRARY_SECRET); - - // secret.Schema - - Linker.link(secret_schema_get_type, "secret_schema_get_type", LIBRARY_SECRET); - Linker.link(secret_schema_new, "secret_schema_new", LIBRARY_SECRET); - Linker.link(secret_schema_newv, "secret_schema_newv", LIBRARY_SECRET); - Linker.link(secret_schema_ref, "secret_schema_ref", LIBRARY_SECRET); - Linker.link(secret_schema_unref, "secret_schema_unref", LIBRARY_SECRET); - - // secret.SchemaAttribute - - Linker.link(secret_schema_attribute_get_type, "secret_schema_attribute_get_type", LIBRARY_SECRET); - - // secret.Service - - Linker.link(secret_service_get_type, "secret_service_get_type", LIBRARY_SECRET); - Linker.link(secret_service_disconnect, "secret_service_disconnect", LIBRARY_SECRET); - Linker.link(secret_service_get, "secret_service_get", LIBRARY_SECRET); - Linker.link(secret_service_get_finish, "secret_service_get_finish", LIBRARY_SECRET); - Linker.link(secret_service_get_sync, "secret_service_get_sync", LIBRARY_SECRET); - Linker.link(secret_service_open, "secret_service_open", LIBRARY_SECRET); - Linker.link(secret_service_open_finish, "secret_service_open_finish", LIBRARY_SECRET); - Linker.link(secret_service_open_sync, "secret_service_open_sync", LIBRARY_SECRET); - Linker.link(secret_service_clear, "secret_service_clear", LIBRARY_SECRET); - Linker.link(secret_service_clear_finish, "secret_service_clear_finish", LIBRARY_SECRET); - Linker.link(secret_service_clear_sync, "secret_service_clear_sync", LIBRARY_SECRET); - Linker.link(secret_service_create_collection_dbus_path, "secret_service_create_collection_dbus_path", LIBRARY_SECRET); + // secret.Backend + + Linker.link(secret_backend_get_type, "secret_backend_get_type", LIBRARY_SECRET); + Linker.link(secret_backend_get, "secret_backend_get", LIBRARY_SECRET); + Linker.link(secret_backend_get_finish, "secret_backend_get_finish", LIBRARY_SECRET); + + // secret.Collection + + Linker.link(secret_collection_get_type, "secret_collection_get_type", LIBRARY_SECRET); + Linker.link(secret_collection_new_for_dbus_path_finish, "secret_collection_new_for_dbus_path_finish", LIBRARY_SECRET); + Linker.link(secret_collection_new_for_dbus_path_sync, "secret_collection_new_for_dbus_path_sync", LIBRARY_SECRET); + Linker.link(secret_collection_create, "secret_collection_create", LIBRARY_SECRET); + Linker.link(secret_collection_create_finish, "secret_collection_create_finish", LIBRARY_SECRET); + Linker.link(secret_collection_create_sync, "secret_collection_create_sync", LIBRARY_SECRET); + Linker.link(secret_collection_for_alias, "secret_collection_for_alias", LIBRARY_SECRET); + Linker.link(secret_collection_for_alias_finish, "secret_collection_for_alias_finish", LIBRARY_SECRET); + Linker.link(secret_collection_for_alias_sync, "secret_collection_for_alias_sync", LIBRARY_SECRET); + Linker.link(secret_collection_new_for_dbus_path, "secret_collection_new_for_dbus_path", LIBRARY_SECRET); + Linker.link(secret_collection_delete, "secret_collection_delete", LIBRARY_SECRET); + Linker.link(secret_collection_delete_finish, "secret_collection_delete_finish", LIBRARY_SECRET); + Linker.link(secret_collection_delete_sync, "secret_collection_delete_sync", LIBRARY_SECRET); + Linker.link(secret_collection_get_created, "secret_collection_get_created", LIBRARY_SECRET); + Linker.link(secret_collection_get_flags, "secret_collection_get_flags", LIBRARY_SECRET); + Linker.link(secret_collection_get_items, "secret_collection_get_items", LIBRARY_SECRET); + Linker.link(secret_collection_get_label, "secret_collection_get_label", LIBRARY_SECRET); + Linker.link(secret_collection_get_locked, "secret_collection_get_locked", LIBRARY_SECRET); + Linker.link(secret_collection_get_modified, "secret_collection_get_modified", LIBRARY_SECRET); + Linker.link(secret_collection_get_service, "secret_collection_get_service", LIBRARY_SECRET); + Linker.link(secret_collection_load_items, "secret_collection_load_items", LIBRARY_SECRET); + Linker.link(secret_collection_load_items_finish, "secret_collection_load_items_finish", LIBRARY_SECRET); + Linker.link(secret_collection_load_items_sync, "secret_collection_load_items_sync", LIBRARY_SECRET); + Linker.link(secret_collection_refresh, "secret_collection_refresh", LIBRARY_SECRET); + Linker.link(secret_collection_search, "secret_collection_search", LIBRARY_SECRET); + Linker.link(secret_collection_search_finish, "secret_collection_search_finish", LIBRARY_SECRET); + Linker.link(secret_collection_search_for_dbus_paths, "secret_collection_search_for_dbus_paths", LIBRARY_SECRET); + Linker.link(secret_collection_search_for_dbus_paths_finish, "secret_collection_search_for_dbus_paths_finish", LIBRARY_SECRET); + Linker.link(secret_collection_search_for_dbus_paths_sync, "secret_collection_search_for_dbus_paths_sync", LIBRARY_SECRET); + Linker.link(secret_collection_search_sync, "secret_collection_search_sync", LIBRARY_SECRET); + Linker.link(secret_collection_set_label, "secret_collection_set_label", LIBRARY_SECRET); + Linker.link(secret_collection_set_label_finish, "secret_collection_set_label_finish", LIBRARY_SECRET); + Linker.link(secret_collection_set_label_sync, "secret_collection_set_label_sync", LIBRARY_SECRET); + + // secret.Item + + Linker.link(secret_item_get_type, "secret_item_get_type", LIBRARY_SECRET); + Linker.link(secret_item_new_for_dbus_path_finish, "secret_item_new_for_dbus_path_finish", LIBRARY_SECRET); + Linker.link(secret_item_new_for_dbus_path_sync, "secret_item_new_for_dbus_path_sync", LIBRARY_SECRET); + Linker.link(secret_item_create, "secret_item_create", LIBRARY_SECRET); + Linker.link(secret_item_create_finish, "secret_item_create_finish", LIBRARY_SECRET); + Linker.link(secret_item_create_sync, "secret_item_create_sync", LIBRARY_SECRET); + Linker.link(secret_item_load_secrets, "secret_item_load_secrets", LIBRARY_SECRET); + Linker.link(secret_item_load_secrets_finish, "secret_item_load_secrets_finish", LIBRARY_SECRET); + Linker.link(secret_item_load_secrets_sync, "secret_item_load_secrets_sync", LIBRARY_SECRET); + Linker.link(secret_item_new_for_dbus_path, "secret_item_new_for_dbus_path", LIBRARY_SECRET); + Linker.link(secret_item_delete, "secret_item_delete", LIBRARY_SECRET); + Linker.link(secret_item_delete_finish, "secret_item_delete_finish", LIBRARY_SECRET); + Linker.link(secret_item_delete_sync, "secret_item_delete_sync", LIBRARY_SECRET); + Linker.link(secret_item_get_attributes, "secret_item_get_attributes", LIBRARY_SECRET); + Linker.link(secret_item_get_created, "secret_item_get_created", LIBRARY_SECRET); + Linker.link(secret_item_get_flags, "secret_item_get_flags", LIBRARY_SECRET); + Linker.link(secret_item_get_label, "secret_item_get_label", LIBRARY_SECRET); + Linker.link(secret_item_get_locked, "secret_item_get_locked", LIBRARY_SECRET); + Linker.link(secret_item_get_modified, "secret_item_get_modified", LIBRARY_SECRET); + Linker.link(secret_item_get_schema_name, "secret_item_get_schema_name", LIBRARY_SECRET); + Linker.link(secret_item_get_secret, "secret_item_get_secret", LIBRARY_SECRET); + Linker.link(secret_item_get_service, "secret_item_get_service", LIBRARY_SECRET); + Linker.link(secret_item_load_secret, "secret_item_load_secret", LIBRARY_SECRET); + Linker.link(secret_item_load_secret_finish, "secret_item_load_secret_finish", LIBRARY_SECRET); + Linker.link(secret_item_load_secret_sync, "secret_item_load_secret_sync", LIBRARY_SECRET); + Linker.link(secret_item_refresh, "secret_item_refresh", LIBRARY_SECRET); + Linker.link(secret_item_set_attributes, "secret_item_set_attributes", LIBRARY_SECRET); + Linker.link(secret_item_set_attributes_finish, "secret_item_set_attributes_finish", LIBRARY_SECRET); + Linker.link(secret_item_set_attributes_sync, "secret_item_set_attributes_sync", LIBRARY_SECRET); + Linker.link(secret_item_set_label, "secret_item_set_label", LIBRARY_SECRET); + Linker.link(secret_item_set_label_finish, "secret_item_set_label_finish", LIBRARY_SECRET); + Linker.link(secret_item_set_label_sync, "secret_item_set_label_sync", LIBRARY_SECRET); + Linker.link(secret_item_set_secret, "secret_item_set_secret", LIBRARY_SECRET); + Linker.link(secret_item_set_secret_finish, "secret_item_set_secret_finish", LIBRARY_SECRET); + Linker.link(secret_item_set_secret_sync, "secret_item_set_secret_sync", LIBRARY_SECRET); + + // secret.Prompt + + Linker.link(secret_prompt_get_type, "secret_prompt_get_type", LIBRARY_SECRET); + Linker.link(secret_prompt_perform, "secret_prompt_perform", LIBRARY_SECRET); + Linker.link(secret_prompt_perform_finish, "secret_prompt_perform_finish", LIBRARY_SECRET); + Linker.link(secret_prompt_perform_sync, "secret_prompt_perform_sync", LIBRARY_SECRET); + Linker.link(secret_prompt_run, "secret_prompt_run", LIBRARY_SECRET); + + // secret.Retrievable + + Linker.link(secret_retrievable_get_type, "secret_retrievable_get_type", LIBRARY_SECRET); + Linker.link(secret_retrievable_get_attributes, "secret_retrievable_get_attributes", LIBRARY_SECRET); + Linker.link(secret_retrievable_get_created, "secret_retrievable_get_created", LIBRARY_SECRET); + Linker.link(secret_retrievable_get_label, "secret_retrievable_get_label", LIBRARY_SECRET); + Linker.link(secret_retrievable_get_modified, "secret_retrievable_get_modified", LIBRARY_SECRET); + Linker.link(secret_retrievable_retrieve_secret, "secret_retrievable_retrieve_secret", LIBRARY_SECRET); + Linker.link(secret_retrievable_retrieve_secret_finish, "secret_retrievable_retrieve_secret_finish", LIBRARY_SECRET); + Linker.link(secret_retrievable_retrieve_secret_sync, "secret_retrievable_retrieve_secret_sync", LIBRARY_SECRET); + + // secret.Schema + + Linker.link(secret_schema_get_type, "secret_schema_get_type", LIBRARY_SECRET); + Linker.link(secret_schema_new, "secret_schema_new", LIBRARY_SECRET); + Linker.link(secret_schema_newv, "secret_schema_newv", LIBRARY_SECRET); + Linker.link(secret_schema_ref, "secret_schema_ref", LIBRARY_SECRET); + Linker.link(secret_schema_unref, "secret_schema_unref", LIBRARY_SECRET); + Linker.link(secret_get_schema, "secret_get_schema", LIBRARY_SECRET); + + // secret.SchemaAttribute + + Linker.link(secret_schema_attribute_get_type, "secret_schema_attribute_get_type", LIBRARY_SECRET); + + // secret.Service + + Linker.link(secret_service_get_type, "secret_service_get_type", LIBRARY_SECRET); + Linker.link(secret_service_disconnect, "secret_service_disconnect", LIBRARY_SECRET); + Linker.link(secret_service_get, "secret_service_get", LIBRARY_SECRET); + Linker.link(secret_service_get_finish, "secret_service_get_finish", LIBRARY_SECRET); + Linker.link(secret_service_get_sync, "secret_service_get_sync", LIBRARY_SECRET); + Linker.link(secret_service_open, "secret_service_open", LIBRARY_SECRET); + Linker.link(secret_service_open_finish, "secret_service_open_finish", LIBRARY_SECRET); + Linker.link(secret_service_open_sync, "secret_service_open_sync", LIBRARY_SECRET); + Linker.link(secret_service_clear, "secret_service_clear", LIBRARY_SECRET); + Linker.link(secret_service_clear_finish, "secret_service_clear_finish", LIBRARY_SECRET); + Linker.link(secret_service_clear_sync, "secret_service_clear_sync", LIBRARY_SECRET); + Linker.link(secret_service_create_collection_dbus_path, "secret_service_create_collection_dbus_path", LIBRARY_SECRET); Linker.link(secret_service_create_collection_dbus_path_finish, "secret_service_create_collection_dbus_path_finish", LIBRARY_SECRET); Linker.link(secret_service_create_collection_dbus_path_sync, "secret_service_create_collection_dbus_path_sync", LIBRARY_SECRET); Linker.link(secret_service_create_item_dbus_path, "secret_service_create_item_dbus_path", LIBRARY_SECRET); @@ -217,6 +218,46 @@ shared static this() Linker.link(secret_value_ref, "secret_value_ref", LIBRARY_SECRET); Linker.link(secret_value_unref, "secret_value_unref", LIBRARY_SECRET); Linker.link(secret_value_unref_to_password, "secret_value_unref_to_password", LIBRARY_SECRET); + + // secret.Attributes + + Linker.link(secret_attributes_build, "secret_attributes_build", LIBRARY_SECRET); + Linker.link(secret_attributes_buildv, "secret_attributes_buildv", LIBRARY_SECRET); + + // secret.Password + + Linker.link(secret_password_clear, "secret_password_clear", LIBRARY_SECRET); + Linker.link(secret_password_clear_finish, "secret_password_clear_finish", LIBRARY_SECRET); + Linker.link(secret_password_clear_sync, "secret_password_clear_sync", LIBRARY_SECRET); + Linker.link(secret_password_clearv, "secret_password_clearv", LIBRARY_SECRET); + Linker.link(secret_password_clearv_sync, "secret_password_clearv_sync", LIBRARY_SECRET); + Linker.link(secret_password_free, "secret_password_free", LIBRARY_SECRET); + Linker.link(secret_password_lookup, "secret_password_lookup", LIBRARY_SECRET); + Linker.link(secret_password_lookup_binary_finish, "secret_password_lookup_binary_finish", LIBRARY_SECRET); + Linker.link(secret_password_lookup_binary_sync, "secret_password_lookup_binary_sync", LIBRARY_SECRET); + Linker.link(secret_password_lookup_finish, "secret_password_lookup_finish", LIBRARY_SECRET); + Linker.link(secret_password_lookup_nonpageable_finish, "secret_password_lookup_nonpageable_finish", LIBRARY_SECRET); + Linker.link(secret_password_lookup_nonpageable_sync, "secret_password_lookup_nonpageable_sync", LIBRARY_SECRET); + Linker.link(secret_password_lookup_sync, "secret_password_lookup_sync", LIBRARY_SECRET); + Linker.link(secret_password_lookupv, "secret_password_lookupv", LIBRARY_SECRET); + Linker.link(secret_password_lookupv_binary_sync, "secret_password_lookupv_binary_sync", LIBRARY_SECRET); + Linker.link(secret_password_lookupv_nonpageable_sync, "secret_password_lookupv_nonpageable_sync", LIBRARY_SECRET); + Linker.link(secret_password_lookupv_sync, "secret_password_lookupv_sync", LIBRARY_SECRET); + Linker.link(secret_password_search, "secret_password_search", LIBRARY_SECRET); + Linker.link(secret_password_search_finish, "secret_password_search_finish", LIBRARY_SECRET); + Linker.link(secret_password_search_sync, "secret_password_search_sync", LIBRARY_SECRET); + Linker.link(secret_password_searchv, "secret_password_searchv", LIBRARY_SECRET); + Linker.link(secret_password_searchv_sync, "secret_password_searchv_sync", LIBRARY_SECRET); + Linker.link(secret_password_store, "secret_password_store", LIBRARY_SECRET); + Linker.link(secret_password_store_binary, "secret_password_store_binary", LIBRARY_SECRET); + Linker.link(secret_password_store_binary_sync, "secret_password_store_binary_sync", LIBRARY_SECRET); + Linker.link(secret_password_store_finish, "secret_password_store_finish", LIBRARY_SECRET); + Linker.link(secret_password_store_sync, "secret_password_store_sync", LIBRARY_SECRET); + Linker.link(secret_password_storev, "secret_password_storev", LIBRARY_SECRET); + Linker.link(secret_password_storev_binary, "secret_password_storev_binary", LIBRARY_SECRET); + Linker.link(secret_password_storev_binary_sync, "secret_password_storev_binary_sync", LIBRARY_SECRET); + Linker.link(secret_password_storev_sync, "secret_password_storev_sync", LIBRARY_SECRET); + Linker.link(secret_password_wipe, "secret_password_wipe", LIBRARY_SECRET); } __gshared extern(C) @@ -328,6 +369,7 @@ __gshared extern(C) SecretSchema* function(const(char)* name, SecretSchemaFlags flags, GHashTable* attributeNamesAndTypes) c_secret_schema_newv; SecretSchema* function(SecretSchema* schema) c_secret_schema_ref; void function(SecretSchema* schema) c_secret_schema_unref; + SecretSchema* function(SecretSchemaType type) c_secret_get_schema; // secret.SchemaAttribute @@ -426,6 +468,46 @@ __gshared extern(C) SecretValue* function(SecretValue* value) c_secret_value_ref; void function(void* value) c_secret_value_unref; char* function(SecretValue* value, size_t* length) c_secret_value_unref_to_password; + + // secret.Attributes + + GHashTable* function(SecretSchema* schema, ... ) c_secret_attributes_build; + GHashTable* function(SecretSchema* schema, void* va) c_secret_attributes_buildv; + + // secret.Password + + void function(SecretSchema* schema, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData, ... ) c_secret_password_clear; + int function(GAsyncResult* result, GError** err) c_secret_password_clear_finish; + int function(SecretSchema* schema, GCancellable* cancellable, GError** error, ... ) c_secret_password_clear_sync; + void function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData) c_secret_password_clearv; + int function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GError** err) c_secret_password_clearv_sync; + void function(char* password) c_secret_password_free; + void function(SecretSchema* schema, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData, ... ) c_secret_password_lookup; + SecretValue* function(GAsyncResult* result, GError** err) c_secret_password_lookup_binary_finish; + SecretValue* function(SecretSchema* schema, GCancellable* cancellable, GError** error, ... ) c_secret_password_lookup_binary_sync; + char* function(GAsyncResult* result, GError** err) c_secret_password_lookup_finish; + char* function(GAsyncResult* result, GError** err) c_secret_password_lookup_nonpageable_finish; + char* function(SecretSchema* schema, GCancellable* cancellable, GError** error, ... ) c_secret_password_lookup_nonpageable_sync; + char* function(SecretSchema* schema, GCancellable* cancellable, GError** error, ... ) c_secret_password_lookup_sync; + void function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData) c_secret_password_lookupv; + SecretValue* function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GError** err) c_secret_password_lookupv_binary_sync; + char* function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GError** err) c_secret_password_lookupv_nonpageable_sync; + char* function(SecretSchema* schema, GHashTable* attributes, GCancellable* cancellable, GError** err) c_secret_password_lookupv_sync; + void function(SecretSchema* schema, SecretSearchFlags flags, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData, ... ) c_secret_password_search; + GList* function(GAsyncResult* result, GError** err) c_secret_password_search_finish; + GList* function(SecretSchema* schema, SecretSearchFlags flags, GCancellable* cancellable, GError** error, ... ) c_secret_password_search_sync; + void function(SecretSchema* schema, GHashTable* attributes, SecretSearchFlags flags, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData) c_secret_password_searchv; + GList* function(SecretSchema* schema, GHashTable* attributes, SecretSearchFlags flags, GCancellable* cancellable, GError** err) c_secret_password_searchv_sync; + void function(SecretSchema* schema, const(char)* collection, const(char)* label, const(char)* password, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData, ... ) c_secret_password_store; + void function(SecretSchema* schema, const(char)* collection, const(char)* label, SecretValue* value, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData, ... ) c_secret_password_store_binary; + int function(SecretSchema* schema, const(char)* collection, const(char)* label, SecretValue* value, GCancellable* cancellable, GError** error, ... ) c_secret_password_store_binary_sync; + int function(GAsyncResult* result, GError** err) c_secret_password_store_finish; + int function(SecretSchema* schema, const(char)* collection, const(char)* label, const(char)* password, GCancellable* cancellable, GError** error, ... ) c_secret_password_store_sync; + void function(SecretSchema* schema, GHashTable* attributes, const(char)* collection, const(char)* label, const(char)* password, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData) c_secret_password_storev; + void function(SecretSchema* schema, GHashTable* attributes, const(char)* collection, const(char)* label, SecretValue* value, GCancellable* cancellable, GAsyncReadyCallback callback, void* userData) c_secret_password_storev_binary; + int function(SecretSchema* schema, GHashTable* attributes, const(char)* collection, const(char)* label, SecretValue* value, GCancellable* cancellable, GError** err) c_secret_password_storev_binary_sync; + int function(SecretSchema* schema, GHashTable* attributes, const(char)* collection, const(char)* label, const(char)* password, GCancellable* cancellable, GError** err) c_secret_password_storev_sync; + void function(char* password) c_secret_password_wipe; } @@ -535,6 +617,7 @@ alias c_secret_schema_new secret_schema_new; alias c_secret_schema_newv secret_schema_newv; alias c_secret_schema_ref secret_schema_ref; alias c_secret_schema_unref secret_schema_unref; +alias c_secret_get_schema secret_get_schema; // secret.SchemaAttribute @@ -633,3 +716,43 @@ alias c_secret_value_get_text secret_value_get_text; alias c_secret_value_ref secret_value_ref; alias c_secret_value_unref secret_value_unref; alias c_secret_value_unref_to_password secret_value_unref_to_password; + +// secret.Attributes + +alias c_secret_attributes_build secret_attributes_build; +alias c_secret_attributes_buildv secret_attributes_buildv; + +// secret.Password + +alias c_secret_password_clear secret_password_clear; +alias c_secret_password_clear_finish secret_password_clear_finish; +alias c_secret_password_clear_sync secret_password_clear_sync; +alias c_secret_password_clearv secret_password_clearv; +alias c_secret_password_clearv_sync secret_password_clearv_sync; +alias c_secret_password_free secret_password_free; +alias c_secret_password_lookup secret_password_lookup; +alias c_secret_password_lookup_binary_finish secret_password_lookup_binary_finish; +alias c_secret_password_lookup_binary_sync secret_password_lookup_binary_sync; +alias c_secret_password_lookup_finish secret_password_lookup_finish; +alias c_secret_password_lookup_nonpageable_finish secret_password_lookup_nonpageable_finish; +alias c_secret_password_lookup_nonpageable_sync secret_password_lookup_nonpageable_sync; +alias c_secret_password_lookup_sync secret_password_lookup_sync; +alias c_secret_password_lookupv secret_password_lookupv; +alias c_secret_password_lookupv_binary_sync secret_password_lookupv_binary_sync; +alias c_secret_password_lookupv_nonpageable_sync secret_password_lookupv_nonpageable_sync; +alias c_secret_password_lookupv_sync secret_password_lookupv_sync; +alias c_secret_password_search secret_password_search; +alias c_secret_password_search_finish secret_password_search_finish; +alias c_secret_password_search_sync secret_password_search_sync; +alias c_secret_password_searchv secret_password_searchv; +alias c_secret_password_searchv_sync secret_password_searchv_sync; +alias c_secret_password_store secret_password_store; +alias c_secret_password_store_binary secret_password_store_binary; +alias c_secret_password_store_binary_sync secret_password_store_binary_sync; +alias c_secret_password_store_finish secret_password_store_finish; +alias c_secret_password_store_sync secret_password_store_sync; +alias c_secret_password_storev secret_password_storev; +alias c_secret_password_storev_binary secret_password_storev_binary; +alias c_secret_password_storev_binary_sync secret_password_storev_binary_sync; +alias c_secret_password_storev_sync secret_password_storev_sync; +alias c_secret_password_wipe secret_password_wipe; diff --git a/frontends/common/linux/secret/c/types.d b/keyring/libsecret/secret/c/types.d similarity index 80% rename from frontends/common/linux/secret/c/types.d rename to keyring/libsecret/secret/c/types.d index c8af398..3abb509 100644 --- a/frontends/common/linux/secret/c/types.d +++ b/keyring/libsecret/secret/c/types.d @@ -12,20 +12,20 @@ public import gobject.c.types; */ public enum SecretBackendFlags { - /** - * no flags for initializing the #SecretBackend - */ - NONE = 0, - /** - * establish a session for transfer of secrets - * while initializing the #SecretBackend - */ - OPEN_SESSION = 2, - /** - * load collections while initializing the - * #SecretBackend - */ - LOAD_COLLECTIONS = 4, + /** + * no flags for initializing the #SecretBackend + */ + NONE = 0, + /** + * establish a session for transfer of secrets + * while initializing the #SecretBackend + */ + OPEN_SESSION = 2, + /** + * load collections while initializing the + * #SecretBackend + */ + LOAD_COLLECTIONS = 4, } alias SecretBackendFlags BackendFlags; @@ -34,10 +34,10 @@ alias SecretBackendFlags BackendFlags; */ public enum SecretCollectionCreateFlags { - /** - * no flags - */ - NONE = 0, + /** + * no flags + */ + NONE = 0, } alias SecretCollectionCreateFlags CollectionCreateFlags; @@ -46,14 +46,14 @@ alias SecretCollectionCreateFlags CollectionCreateFlags; */ public enum SecretCollectionFlags { - /** - * no flags - */ - NONE = 0, - /** - * items have or should be loaded - */ - LOAD_ITEMS = 2, + /** + * no flags + */ + NONE = 0, + /** + * items have or should be loaded + */ + LOAD_ITEMS = 2, } alias SecretCollectionFlags CollectionFlags; @@ -65,29 +65,29 @@ alias SecretCollectionFlags CollectionFlags; */ public enum SecretError { - /** - * received an invalid data or message from the Secret - * Service - */ - PROTOCOL = 1, - /** - * the item or collection is locked and the operation - * cannot be performed - */ - IS_LOCKED = 2, - /** - * no such item or collection found in the Secret - * Service - */ - NO_SUCH_OBJECT = 3, - /** - * a relevant item or collection already exists - */ - ALREADY_EXISTS = 4, - /** - * the file format is not valid - */ - INVALID_FILE_FORMAT = 5, + /** + * received an invalid data or message from the Secret + * Service + */ + PROTOCOL = 1, + /** + * the item or collection is locked and the operation + * cannot be performed + */ + IS_LOCKED = 2, + /** + * no such item or collection found in the Secret + * Service + */ + NO_SUCH_OBJECT = 3, + /** + * a relevant item or collection already exists + */ + ALREADY_EXISTS = 4, + /** + * the file format is not valid + */ + INVALID_FILE_FORMAT = 5, } alias SecretError Error; @@ -96,14 +96,14 @@ alias SecretError Error; */ public enum SecretItemCreateFlags { - /** - * no flags - */ - NONE = 0, - /** - * replace an item with the same attributes. - */ - REPLACE = 2, + /** + * no flags + */ + NONE = 0, + /** + * replace an item with the same attributes. + */ + REPLACE = 2, } alias SecretItemCreateFlags ItemCreateFlags; @@ -112,14 +112,14 @@ alias SecretItemCreateFlags ItemCreateFlags; */ public enum SecretItemFlags { - /** - * no flags - */ - NONE = 0, - /** - * a secret has been (or should be) loaded for #SecretItem - */ - LOAD_SECRET = 2, + /** + * no flags + */ + NONE = 0, + /** + * a secret has been (or should be) loaded for #SecretItem + */ + LOAD_SECRET = 2, } alias SecretItemFlags ItemFlags; @@ -132,18 +132,18 @@ alias SecretItemFlags ItemFlags; */ public enum SecretSchemaAttributeType { - /** - * a utf-8 string attribute - */ - STRING = 0, - /** - * an integer attribute, stored as a decimal - */ - INTEGER = 1, - /** - * a boolean attribute, stored as 'true' or 'false' - */ - BOOLEAN = 2, + /** + * a utf-8 string attribute + */ + STRING = 0, + /** + * an integer attribute, stored as a decimal + */ + INTEGER = 1, + /** + * a boolean attribute, stored as 'true' or 'false' + */ + BOOLEAN = 2, } alias SecretSchemaAttributeType SchemaAttributeType; @@ -152,15 +152,15 @@ alias SecretSchemaAttributeType SchemaAttributeType; */ public enum SecretSchemaFlags { - /** - * no flags for the schema - */ - NONE = 0, - /** - * don't match the schema name when looking up or - * removing passwords - */ - DONT_MATCH_NAME = 2, + /** + * no flags for the schema + */ + NONE = 0, + /** + * don't match the schema name when looking up or + * removing passwords + */ + DONT_MATCH_NAME = 2, } alias SecretSchemaFlags SchemaFlags; @@ -227,15 +227,15 @@ alias SecretSchemaFlags SchemaFlags; */ public enum SecretSchemaType { - /** - * Personal passwords - */ - NOTE = 0, - /** - * Network passwords from older - * libgnome-keyring storage - */ - COMPAT_NETWORK = 1, + /** + * Personal passwords + */ + NOTE = 0, + /** + * Network passwords from older + * libgnome-keyring storage + */ + COMPAT_NETWORK = 1, } alias SecretSchemaType SchemaType; @@ -244,22 +244,22 @@ alias SecretSchemaType SchemaType; */ public enum SecretSearchFlags { - /** - * no flags - */ - NONE = 0, - /** - * all the items matching the search will be returned, instead of just the first one - */ - ALL = 2, - /** - * unlock locked items while searching - */ - UNLOCK = 4, - /** - * while searching load secrets for items that are not locked - */ - LOAD_SECRETS = 8, + /** + * no flags + */ + NONE = 0, + /** + * all the items matching the search will be returned, instead of just the first one + */ + ALL = 2, + /** + * unlock locked items while searching + */ + UNLOCK = 4, + /** + * while searching load secrets for items that are not locked + */ + LOAD_SECRETS = 8, } alias SecretSearchFlags SearchFlags; @@ -269,20 +269,20 @@ alias SecretSearchFlags SearchFlags; */ public enum SecretServiceFlags { - /** - * no flags for initializing the #SecretService - */ - NONE = 0, - /** - * establish a session for transfer of secrets - * while initializing the #SecretService - */ - OPEN_SESSION = 2, - /** - * load collections while initializing the - * #SecretService - */ - LOAD_COLLECTIONS = 4, + /** + * no flags for initializing the #SecretService + */ + NONE = 0, + /** + * establish a session for transfer of secrets + * while initializing the #SecretService + */ + OPEN_SESSION = 2, + /** + * load collections while initializing the + * #SecretService + */ + LOAD_COLLECTIONS = 4, } alias SecretServiceFlags ServiceFlags; @@ -295,7 +295,7 @@ struct SecretBackend; */ struct SecretBackendInterface { - /** + /** * the parent interface */ GTypeInterface parentIface; @@ -553,11 +553,11 @@ alias SECRET_MAJOR_VERSION = MAJOR_VERSION; /** * The micro version of libsecret. */ -enum MICRO_VERSION = 5; +enum MICRO_VERSION = 1; alias SECRET_MICRO_VERSION = MICRO_VERSION; /** * The minor version of libsecret. */ -enum MINOR_VERSION = 20; +enum MINOR_VERSION = 21; alias SECRET_MINOR_VERSION = MINOR_VERSION; diff --git a/keyring/source/keyring.d b/keyring/source/keyring.d new file mode 100644 index 0000000..335dddb --- /dev/null +++ b/keyring/source/keyring.d @@ -0,0 +1,50 @@ +module keyring; + +import libsecretkeyring; +import osxkeyring; +import windowskeyring; +import memorykeyring; + +interface KeyringImplementation +{ + void store(string account); + string lookup(); + void clear(); +} + +struct Keyring +{ + KeyringImplementation backend; + + void store(string account) + { + + } +} + +Keyring makeKeyring() +{ + version (Windows) + { + if (auto keyring = WindowsKeyring.create()) + { + return Keyring(keyring); + } + } + else version (OSX) + { + if (auto keyring = OSXKeyring.create()) + { + return Keyring(keyring); + } + } + else version (LibSecret) + { + if (auto keyring = LibSecretKeyring.create()) + { + return Keyring(keyring); + } + } + + return Keyring(new MemoryKeyring()); +} diff --git a/keyring/source/libsecretkeyring.d b/keyring/source/libsecretkeyring.d new file mode 100644 index 0000000..1e7f87b --- /dev/null +++ b/keyring/source/libsecretkeyring.d @@ -0,0 +1,61 @@ +module libsecretkeyring; + +import keyring; + +version (LibSecret): + +import slf4d; + +import gio.SimpleAsyncResult; + +import glib.c.functions; +import glib.HashTable; + +import secret.Password; +import secret.Schema; +import secret.Service; + +import utils; + +class LibSecretKeyring : KeyringImplementation +{ + Schema schema; + + this() + { + auto typeHashTable = new HashTable(g_str_hash, g_str_equal); + schema = new Schema( + "dev.dadoum.Sideloader", SchemaFlags.NONE, + typeHashTable + ); + } + + static LibSecretKeyring create() + { + return new LibSecretKeyring(); + } + + void store(string account) + { + auto accountEntry = new HashTable(g_str_hash, g_str_equal); + // Password.storev(schema, accountEntry, COLLECTION_DEFAULT, "account", account, null, c!((GObject* sourceObject, GAsyncResult* res) { + // Password.storeFinish(new SimpleAsyncResult(cast(GSimpleAsyncResult*)res, false)); + // }).expand); + Password.storevSync(schema, accountEntry, COLLECTION_DEFAULT, "account", account, null); + } + + string lookup() + { + auto accountEntry = new HashTable(g_str_hash, g_str_equal); + // Password.lookupv(schema, accountEntry, null, c!((GObject* sourceObject, GAsyncResult* res) { + // + // }).expand); + return Password.lookupvSync(schema, accountEntry, null); + } + + void clear() + { + auto accountEntry = new HashTable(g_str_hash, g_str_equal); + Password.clearvSync(schema, accountEntry, null); + } +} diff --git a/keyring/source/memorykeyring.d b/keyring/source/memorykeyring.d new file mode 100644 index 0000000..6c8f618 --- /dev/null +++ b/keyring/source/memorykeyring.d @@ -0,0 +1,23 @@ +module memorykeyring; + +import keyring; + +class MemoryKeyring : KeyringImplementation +{ + string account; + + void store(string account) + { + this.account = account; + } + + string lookup() + { + return account; + } + + void clear() + { + account = null; + } +} diff --git a/keyring/source/osxkeyring.d b/keyring/source/osxkeyring.d new file mode 100644 index 0000000..95444e6 --- /dev/null +++ b/keyring/source/osxkeyring.d @@ -0,0 +1,21 @@ +module osxkeyring; + +import keyring; + +version (OSX): + +class OSXKeyring : KeyringImplementation +{ + static OSXKeyring create() + { + return null; // new OSXKeyring; + } + + void store(string account) + { + } + + void withAccount(void delegate(string account) handler) + { + } +} diff --git a/keyring/source/windows/win32/security/credentials/package.d b/keyring/source/windows/win32/security/credentials/package.d new file mode 100644 index 0000000..6ca7adc --- /dev/null +++ b/keyring/source/windows/win32/security/credentials/package.d @@ -0,0 +1,818 @@ +module windows.win32.security.credentials; + +import core.sys.windows.basetyps; +import core.sys.windows.winbase; +import core.sys.windows.windef; +import core.sys.windows.winuser; + +version (Windows): +extern (Windows): + +alias CRED_FLAGS = uint; +enum : uint +{ + CRED_FLAGS_PASSWORD_FOR_CERT = 0x00000001, + CRED_FLAGS_PROMPT_NOW = 0x00000002, + CRED_FLAGS_USERNAME_TARGET = 0x00000004, + CRED_FLAGS_OWF_CRED_BLOB = 0x00000008, + CRED_FLAGS_REQUIRE_CONFIRMATION = 0x00000010, + CRED_FLAGS_WILDCARD_MATCH = 0x00000020, + CRED_FLAGS_VSM_PROTECTED = 0x00000040, + CRED_FLAGS_NGC_CERT = 0x00000080, + CRED_FLAGS_VALID_FLAGS = 0x0000f0ff, + CRED_FLAGS_VALID_INPUT_FLAGS = 0x0000f09f, +} + +alias CRED_TYPE = uint; +enum : uint +{ + CRED_TYPE_GENERIC = 0x00000001, + CRED_TYPE_DOMAIN_PASSWORD = 0x00000002, + CRED_TYPE_DOMAIN_CERTIFICATE = 0x00000003, + CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0x00000004, + CRED_TYPE_GENERIC_CERTIFICATE = 0x00000005, + CRED_TYPE_DOMAIN_EXTENDED = 0x00000006, + CRED_TYPE_MAXIMUM = 0x00000007, + CRED_TYPE_MAXIMUM_EX = 0x000003ef, +} + +alias CRED_PERSIST = uint; +enum : uint +{ + CRED_PERSIST_NONE = 0x00000000, + CRED_PERSIST_SESSION = 0x00000001, + CRED_PERSIST_LOCAL_MACHINE = 0x00000002, + CRED_PERSIST_ENTERPRISE = 0x00000003, +} + +alias CREDUI_FLAGS = uint; +enum : uint +{ + CREDUI_FLAGS_ALWAYS_SHOW_UI = 0x00000080, + CREDUI_FLAGS_COMPLETE_USERNAME = 0x00000800, + CREDUI_FLAGS_DO_NOT_PERSIST = 0x00000002, + CREDUI_FLAGS_EXCLUDE_CERTIFICATES = 0x00000008, + CREDUI_FLAGS_EXPECT_CONFIRMATION = 0x00020000, + CREDUI_FLAGS_GENERIC_CREDENTIALS = 0x00040000, + CREDUI_FLAGS_INCORRECT_PASSWORD = 0x00000001, + CREDUI_FLAGS_KEEP_USERNAME = 0x00100000, + CREDUI_FLAGS_PASSWORD_ONLY_OK = 0x00000200, + CREDUI_FLAGS_PERSIST = 0x00001000, + CREDUI_FLAGS_REQUEST_ADMINISTRATOR = 0x00000004, + CREDUI_FLAGS_REQUIRE_CERTIFICATE = 0x00000010, + CREDUI_FLAGS_REQUIRE_SMARTCARD = 0x00000100, + CREDUI_FLAGS_SERVER_CREDENTIAL = 0x00004000, + CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX = 0x00000040, + CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS = 0x00080000, + CREDUI_FLAGS_VALIDATE_USERNAME = 0x00000400, +} + +alias SCARD_SCOPE = uint; +enum : uint +{ + SCARD_SCOPE_USER = 0x00000000, + SCARD_SCOPE_SYSTEM = 0x00000002, +} + +alias CRED_ENUMERATE_FLAGS = uint; +enum : uint +{ + CRED_ENUMERATE_ALL_CREDENTIALS = 0x00000001, +} + +alias CREDUIWIN_FLAGS = uint; +enum : uint +{ + CREDUIWIN_GENERIC = 0x00000001, + CREDUIWIN_CHECKBOX = 0x00000002, + CREDUIWIN_AUTHPACKAGE_ONLY = 0x00000010, + CREDUIWIN_IN_CRED_ONLY = 0x00000020, + CREDUIWIN_ENUMERATE_ADMINS = 0x00000100, + CREDUIWIN_ENUMERATE_CURRENT_USER = 0x00000200, + CREDUIWIN_SECURE_PROMPT = 0x00001000, + CREDUIWIN_PREPROMPTING = 0x00002000, + CREDUIWIN_PACK_32_WOW = 0x10000000, +} + +alias SCARD_STATE = uint; +enum : uint +{ + SCARD_STATE_UNAWARE = 0x00000000, + SCARD_STATE_IGNORE = 0x00000001, + SCARD_STATE_UNAVAILABLE = 0x00000008, + SCARD_STATE_EMPTY = 0x00000010, + SCARD_STATE_PRESENT = 0x00000020, + SCARD_STATE_ATRMATCH = 0x00000040, + SCARD_STATE_EXCLUSIVE = 0x00000080, + SCARD_STATE_INUSE = 0x00000100, + SCARD_STATE_MUTE = 0x00000200, + SCARD_STATE_CHANGED = 0x00000002, + SCARD_STATE_UNKNOWN = 0x00000004, +} + +alias CRED_PACK_FLAGS = uint; +enum : uint +{ + CRED_PACK_PROTECTED_CREDENTIALS = 0x00000001, + CRED_PACK_WOW_BUFFER = 0x00000002, + CRED_PACK_GENERIC_CREDENTIALS = 0x00000004, + CRED_PACK_ID_PROVIDER_CREDENTIALS = 0x00000008, +} + +HRESULT KeyCredentialManagerGetOperationErrorStates(KeyCredentialManagerOperationType, BOOL*, KeyCredentialManagerOperationErrorStates*); +HRESULT KeyCredentialManagerShowUIOperation(HWND, KeyCredentialManagerOperationType); +HRESULT KeyCredentialManagerGetInformation(KeyCredentialManagerInfo**); +void KeyCredentialManagerFreeInformation(KeyCredentialManagerInfo*); +BOOL CredWriteW(CREDENTIALW*, uint); +BOOL CredWriteA(CREDENTIALA*, uint); +BOOL CredReadW(const(wchar)*, CRED_TYPE, uint, CREDENTIALW**); +BOOL CredReadA(const(char)*, CRED_TYPE, uint, CREDENTIALA**); +BOOL CredEnumerateW(const(wchar)*, CRED_ENUMERATE_FLAGS, uint*, CREDENTIALW***); +BOOL CredEnumerateA(const(char)*, CRED_ENUMERATE_FLAGS, uint*, CREDENTIALA***); +BOOL CredWriteDomainCredentialsW(CREDENTIAL_TARGET_INFORMATIONW*, CREDENTIALW*, uint); +BOOL CredWriteDomainCredentialsA(CREDENTIAL_TARGET_INFORMATIONA*, CREDENTIALA*, uint); +BOOL CredReadDomainCredentialsW(CREDENTIAL_TARGET_INFORMATIONW*, uint, uint*, CREDENTIALW***); +BOOL CredReadDomainCredentialsA(CREDENTIAL_TARGET_INFORMATIONA*, uint, uint*, CREDENTIALA***); +BOOL CredDeleteW(const(wchar)*, CRED_TYPE, uint); +BOOL CredDeleteA(const(char)*, CRED_TYPE, uint); +BOOL CredRenameW(const(wchar)*, const(wchar)*, CRED_TYPE, uint); +BOOL CredRenameA(const(char)*, const(char)*, CRED_TYPE, uint); +BOOL CredGetTargetInfoW(const(wchar)*, uint, CREDENTIAL_TARGET_INFORMATIONW**); +BOOL CredGetTargetInfoA(const(char)*, uint, CREDENTIAL_TARGET_INFORMATIONA**); +BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE, void*, PWSTR*); +BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE, void*, PSTR*); +BOOL CredUnmarshalCredentialW(const(wchar)*, CRED_MARSHAL_TYPE*, void**); +BOOL CredUnmarshalCredentialA(const(char)*, CRED_MARSHAL_TYPE*, void**); +BOOL CredIsMarshaledCredentialW(const(wchar)*); +BOOL CredIsMarshaledCredentialA(const(char)*); +BOOL CredUnPackAuthenticationBufferW(CRED_PACK_FLAGS, void*, uint, PWSTR, uint*, PWSTR, uint*, PWSTR, uint*); +BOOL CredUnPackAuthenticationBufferA(CRED_PACK_FLAGS, void*, uint, PSTR, uint*, PSTR, uint*, PSTR, uint*); +BOOL CredPackAuthenticationBufferW(CRED_PACK_FLAGS, PWSTR, PWSTR, ubyte*, uint*); +BOOL CredPackAuthenticationBufferA(CRED_PACK_FLAGS, PSTR, PSTR, ubyte*, uint*); +BOOL CredProtectW(BOOL, PWSTR, uint, PWSTR, uint*, CRED_PROTECTION_TYPE*); +BOOL CredProtectA(BOOL, PSTR, uint, PSTR, uint*, CRED_PROTECTION_TYPE*); +BOOL CredUnprotectW(BOOL, PWSTR, uint, PWSTR, uint*); +BOOL CredUnprotectA(BOOL, PSTR, uint, PSTR, uint*); +BOOL CredIsProtectedW(PWSTR, CRED_PROTECTION_TYPE*); +BOOL CredIsProtectedA(PSTR, CRED_PROTECTION_TYPE*); +BOOL CredFindBestCredentialW(const(wchar)*, uint, uint, CREDENTIALW**); +BOOL CredFindBestCredentialA(const(char)*, uint, uint, CREDENTIALA**); +BOOL CredGetSessionTypes(uint, uint*); +void CredFree(void*); +uint CredUIPromptForCredentialsW(CREDUI_INFOW*, const(wchar)*, SecHandle*, uint, PWSTR, uint, PWSTR, uint, BOOL*, CREDUI_FLAGS); +uint CredUIPromptForCredentialsA(CREDUI_INFOA*, const(char)*, SecHandle*, uint, PSTR, uint, PSTR, uint, BOOL*, CREDUI_FLAGS); +uint CredUIPromptForWindowsCredentialsW(CREDUI_INFOW*, uint, uint*, const(void)*, uint, void**, uint*, BOOL*, CREDUIWIN_FLAGS); +uint CredUIPromptForWindowsCredentialsA(CREDUI_INFOA*, uint, uint*, const(void)*, uint, void**, uint*, BOOL*, CREDUIWIN_FLAGS); +uint CredUIParseUserNameW(const(wchar)*, PWSTR, uint, PWSTR, uint); +uint CredUIParseUserNameA(const(char)*, PSTR, uint, PSTR, uint); +uint CredUICmdLinePromptForCredentialsW(const(wchar)*, SecHandle*, uint, PWSTR, uint, PWSTR, uint, BOOL*, CREDUI_FLAGS); +uint CredUICmdLinePromptForCredentialsA(const(char)*, SecHandle*, uint, PSTR, uint, PSTR, uint, BOOL*, CREDUI_FLAGS); +uint CredUIConfirmCredentialsW(const(wchar)*, BOOL); +uint CredUIConfirmCredentialsA(const(char)*, BOOL); +uint CredUIStoreSSOCredW(const(wchar)*, const(wchar)*, const(wchar)*, BOOL); +uint CredUIReadSSOCredW(const(wchar)*, PWSTR*); +int SCardEstablishContext(SCARD_SCOPE, const(void)*, const(void)*, ulong*); +int SCardReleaseContext(ulong); +int SCardIsValidContext(ulong); +int SCardListReaderGroupsA(ulong, PSTR, uint*); +int SCardListReaderGroupsW(ulong, PWSTR, uint*); +int SCardListReadersA(ulong, const(char)*, PSTR, uint*); +int SCardListReadersW(ulong, const(wchar)*, PWSTR, uint*); +int SCardListCardsA(ulong, ubyte*, const(GUID)*, uint, PSTR, uint*); +int SCardListCardsW(ulong, ubyte*, const(GUID)*, uint, PWSTR, uint*); +int SCardListInterfacesA(ulong, const(char)*, GUID*, uint*); +int SCardListInterfacesW(ulong, const(wchar)*, GUID*, uint*); +int SCardGetProviderIdA(ulong, const(char)*, GUID*); +int SCardGetProviderIdW(ulong, const(wchar)*, GUID*); +int SCardGetCardTypeProviderNameA(ulong, const(char)*, uint, PSTR, uint*); +int SCardGetCardTypeProviderNameW(ulong, const(wchar)*, uint, PWSTR, uint*); +int SCardIntroduceReaderGroupA(ulong, const(char)*); +int SCardIntroduceReaderGroupW(ulong, const(wchar)*); +int SCardForgetReaderGroupA(ulong, const(char)*); +int SCardForgetReaderGroupW(ulong, const(wchar)*); +int SCardIntroduceReaderA(ulong, const(char)*, const(char)*); +int SCardIntroduceReaderW(ulong, const(wchar)*, const(wchar)*); +int SCardForgetReaderA(ulong, const(char)*); +int SCardForgetReaderW(ulong, const(wchar)*); +int SCardAddReaderToGroupA(ulong, const(char)*, const(char)*); +int SCardAddReaderToGroupW(ulong, const(wchar)*, const(wchar)*); +int SCardRemoveReaderFromGroupA(ulong, const(char)*, const(char)*); +int SCardRemoveReaderFromGroupW(ulong, const(wchar)*, const(wchar)*); +int SCardIntroduceCardTypeA(ulong, const(char)*, const(GUID)*, const(GUID)*, uint, ubyte*, ubyte*, uint); +int SCardIntroduceCardTypeW(ulong, const(wchar)*, const(GUID)*, const(GUID)*, uint, ubyte*, ubyte*, uint); +int SCardSetCardTypeProviderNameA(ulong, const(char)*, uint, const(char)*); +int SCardSetCardTypeProviderNameW(ulong, const(wchar)*, uint, const(wchar)*); +int SCardForgetCardTypeA(ulong, const(char)*); +int SCardForgetCardTypeW(ulong, const(wchar)*); +int SCardFreeMemory(ulong, const(void)*); +HANDLE SCardAccessStartedEvent(); +void SCardReleaseStartedEvent(); +int SCardLocateCardsA(ulong, const(char)*, SCARD_READERSTATEA*, uint); +int SCardLocateCardsW(ulong, const(wchar)*, SCARD_READERSTATEW*, uint); +int SCardLocateCardsByATRA(ulong, SCARD_ATRMASK*, uint, SCARD_READERSTATEA*, uint); +int SCardLocateCardsByATRW(ulong, SCARD_ATRMASK*, uint, SCARD_READERSTATEW*, uint); +int SCardGetStatusChangeA(ulong, uint, SCARD_READERSTATEA*, uint); +int SCardGetStatusChangeW(ulong, uint, SCARD_READERSTATEW*, uint); +int SCardCancel(ulong); +int SCardConnectA(ulong, const(char)*, uint, uint, ulong*, uint*); +int SCardConnectW(ulong, const(wchar)*, uint, uint, ulong*, uint*); +int SCardReconnect(ulong, uint, uint, uint, uint*); +int SCardDisconnect(ulong, uint); +int SCardBeginTransaction(ulong); +int SCardEndTransaction(ulong, uint); +int SCardState(ulong, uint*, uint*, ubyte*, uint*); +int SCardStatusA(ulong, PSTR, uint*, uint*, uint*, ubyte*, uint*); +int SCardStatusW(ulong, PWSTR, uint*, uint*, uint*, ubyte*, uint*); +int SCardTransmit(ulong, SCARD_IO_REQUEST*, ubyte*, uint, SCARD_IO_REQUEST*, ubyte*, uint*); +int SCardGetTransmitCount(ulong, uint*); +int SCardControl(ulong, uint, const(void)*, uint, void*, uint, uint*); +int SCardGetAttrib(ulong, uint, ubyte*, uint*); +int SCardSetAttrib(ulong, uint, ubyte*, uint); +int SCardUIDlgSelectCardA(OPENCARDNAME_EXA*); +int SCardUIDlgSelectCardW(OPENCARDNAME_EXW*); +int GetOpenCardNameA(OPENCARDNAMEA*); +int GetOpenCardNameW(OPENCARDNAMEW*); +int SCardDlgExtendedError(); +int SCardReadCacheA(ulong, GUID*, uint, PSTR, ubyte*, uint*); +int SCardReadCacheW(ulong, GUID*, uint, PWSTR, ubyte*, uint*); +int SCardWriteCacheA(ulong, GUID*, uint, PSTR, ubyte*, uint); +int SCardWriteCacheW(ulong, GUID*, uint, PWSTR, ubyte*, uint); +int SCardGetReaderIconA(ulong, const(char)*, ubyte*, uint*); +int SCardGetReaderIconW(ulong, const(wchar)*, ubyte*, uint*); +int SCardGetDeviceTypeIdA(ulong, const(char)*, uint*); +int SCardGetDeviceTypeIdW(ulong, const(wchar)*, uint*); +int SCardGetReaderDeviceInstanceIdA(ulong, const(char)*, PSTR, uint*); +int SCardGetReaderDeviceInstanceIdW(ulong, const(wchar)*, PWSTR, uint*); +int SCardListReadersWithDeviceInstanceIdA(ulong, const(char)*, PSTR, uint*); +int SCardListReadersWithDeviceInstanceIdW(ulong, const(wchar)*, PWSTR, uint*); +int SCardAudit(ulong, uint); +enum CRED_MAX_CREDENTIAL_BLOB_SIZE = 0x00000a00; +enum CRED_MAX_USERNAME_LENGTH = 0x00000201; +enum CRED_MAX_DOMAIN_TARGET_NAME_LENGTH = 0x00000151; +enum FILE_DEVICE_SMARTCARD = 0x00000031; +enum GUID_DEVINTERFACE_SMARTCARD_READER = GUID(0x50dd5230, 0xba8a, 0x11d1, [0xbf, 0x5d, 0x0, 0x0, 0xf8, 0x5, 0xf5, 0x30]); +enum SCARD_ATR_LENGTH = 0x00000021; +enum SCARD_PROTOCOL_UNDEFINED = 0x00000000; +enum SCARD_PROTOCOL_T0 = 0x00000001; +enum SCARD_PROTOCOL_T1 = 0x00000002; +enum SCARD_PROTOCOL_RAW = 0x00010000; +enum SCARD_PROTOCOL_DEFAULT = 0x80000000; +enum SCARD_PROTOCOL_OPTIMAL = 0x00000000; +enum SCARD_POWER_DOWN = 0x00000000; +enum SCARD_COLD_RESET = 0x00000001; +enum SCARD_WARM_RESET = 0x00000002; +enum MAXIMUM_ATTR_STRING_LENGTH = 0x00000020; +enum MAXIMUM_SMARTCARD_READERS = 0x0000000a; +enum SCARD_CLASS_VENDOR_INFO = 0x00000001; +enum SCARD_CLASS_COMMUNICATIONS = 0x00000002; +enum SCARD_CLASS_PROTOCOL = 0x00000003; +enum SCARD_CLASS_POWER_MGMT = 0x00000004; +enum SCARD_CLASS_SECURITY = 0x00000005; +enum SCARD_CLASS_MECHANICAL = 0x00000006; +enum SCARD_CLASS_VENDOR_DEFINED = 0x00000007; +enum SCARD_CLASS_IFD_PROTOCOL = 0x00000008; +enum SCARD_CLASS_ICC_STATE = 0x00000009; +enum SCARD_CLASS_PERF = 0x00007ffe; +enum SCARD_CLASS_SYSTEM = 0x00007fff; +enum SCARD_T0_HEADER_LENGTH = 0x00000007; +enum SCARD_T0_CMD_LENGTH = 0x00000005; +enum SCARD_T1_PROLOGUE_LENGTH = 0x00000003; +enum SCARD_T1_EPILOGUE_LENGTH = 0x00000002; +enum SCARD_T1_EPILOGUE_LENGTH_LRC = 0x00000001; +enum SCARD_T1_MAX_IFS = 0x000000fe; +enum SCARD_UNKNOWN = 0x00000000; +enum SCARD_ABSENT = 0x00000001; +enum SCARD_PRESENT = 0x00000002; +enum SCARD_SWALLOWED = 0x00000003; +enum SCARD_POWERED = 0x00000004; +enum SCARD_NEGOTIABLE = 0x00000005; +enum SCARD_SPECIFIC = 0x00000006; +enum SCARD_READER_SWALLOWS = 0x00000001; +enum SCARD_READER_EJECTS = 0x00000002; +enum SCARD_READER_CONFISCATES = 0x00000004; +enum SCARD_READER_CONTACTLESS = 0x00000008; +enum SCARD_READER_TYPE_SERIAL = 0x00000001; +enum SCARD_READER_TYPE_PARALELL = 0x00000002; +enum SCARD_READER_TYPE_KEYBOARD = 0x00000004; +enum SCARD_READER_TYPE_SCSI = 0x00000008; +enum SCARD_READER_TYPE_IDE = 0x00000010; +enum SCARD_READER_TYPE_USB = 0x00000020; +enum SCARD_READER_TYPE_PCMCIA = 0x00000040; +enum SCARD_READER_TYPE_TPM = 0x00000080; +enum SCARD_READER_TYPE_NFC = 0x00000100; +enum SCARD_READER_TYPE_UICC = 0x00000200; +enum SCARD_READER_TYPE_NGC = 0x00000400; +enum SCARD_READER_TYPE_EMBEDDEDSE = 0x00000800; +enum SCARD_READER_TYPE_VENDOR = 0x000000f0; +enum STATUS_LOGON_FAILURE = 0xffffffffc000006d; +enum STATUS_WRONG_PASSWORD = 0xffffffffc000006a; +enum STATUS_PASSWORD_EXPIRED = 0xffffffffc0000071; +enum STATUS_PASSWORD_MUST_CHANGE = 0xffffffffc0000224; +enum STATUS_DOWNGRADE_DETECTED = 0xffffffffc0000388; +enum STATUS_AUTHENTICATION_FIREWALL_FAILED = 0xffffffffc0000413; +enum STATUS_ACCOUNT_DISABLED = 0xffffffffc0000072; +enum STATUS_ACCOUNT_RESTRICTION = 0xffffffffc000006e; +enum STATUS_ACCOUNT_LOCKED_OUT = 0xffffffffc0000234; +enum STATUS_ACCOUNT_EXPIRED = 0xffffffffc0000193; +enum STATUS_LOGON_TYPE_NOT_GRANTED = 0xffffffffc000015b; +enum STATUS_NO_SUCH_LOGON_SESSION = 0xffffffffc000005f; +enum STATUS_NO_SUCH_USER = 0xffffffffc0000064; +enum CRED_MAX_STRING_LENGTH = 0x00000100; +enum CRED_MAX_GENERIC_TARGET_NAME_LENGTH = 0x00007fff; +enum CRED_MAX_TARGETNAME_NAMESPACE_LENGTH = 0x00000100; +enum CRED_MAX_TARGETNAME_ATTRIBUTE_LENGTH = 0x00000100; +enum CRED_MAX_VALUE_SIZE = 0x00000100; +enum CRED_MAX_ATTRIBUTES = 0x00000040; +enum CRED_SESSION_WILDCARD_NAME_W = "*Session"; +enum CRED_SESSION_WILDCARD_NAME_A = "*Session"; +enum CRED_TARGETNAME_DOMAIN_NAMESPACE_W = "Domain"; +enum CRED_TARGETNAME_DOMAIN_NAMESPACE_A = "Domain"; +enum CRED_TARGETNAME_LEGACYGENERIC_NAMESPACE_W = "LegacyGeneric"; +enum CRED_TARGETNAME_LEGACYGENERIC_NAMESPACE_A = "LegacyGeneric"; +enum CRED_TARGETNAME_ATTRIBUTE_TARGET_W = "target"; +enum CRED_TARGETNAME_ATTRIBUTE_TARGET_A = "target"; +enum CRED_TARGETNAME_ATTRIBUTE_NAME_W = "name"; +enum CRED_TARGETNAME_ATTRIBUTE_NAME_A = "name"; +enum CRED_TARGETNAME_ATTRIBUTE_BATCH_W = "batch"; +enum CRED_TARGETNAME_ATTRIBUTE_BATCH_A = "batch"; +enum CRED_TARGETNAME_ATTRIBUTE_INTERACTIVE_W = "interactive"; +enum CRED_TARGETNAME_ATTRIBUTE_INTERACTIVE_A = "interactive"; +enum CRED_TARGETNAME_ATTRIBUTE_SERVICE_W = "service"; +enum CRED_TARGETNAME_ATTRIBUTE_SERVICE_A = "service"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORK_W = "network"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORK_A = "network"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORKCLEARTEXT_W = "networkcleartext"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORKCLEARTEXT_A = "networkcleartext"; +enum CRED_TARGETNAME_ATTRIBUTE_REMOTEINTERACTIVE_W = "remoteinteractive"; +enum CRED_TARGETNAME_ATTRIBUTE_REMOTEINTERACTIVE_A = "remoteinteractive"; +enum CRED_TARGETNAME_ATTRIBUTE_CACHEDINTERACTIVE_W = "cachedinteractive"; +enum CRED_TARGETNAME_ATTRIBUTE_CACHEDINTERACTIVE_A = "cachedinteractive"; +enum CRED_SESSION_WILDCARD_NAME = "*Session"; +enum CRED_TARGETNAME_DOMAIN_NAMESPACE = "Domain"; +enum CRED_TARGETNAME_ATTRIBUTE_NAME = "name"; +enum CRED_TARGETNAME_ATTRIBUTE_TARGET = "target"; +enum CRED_TARGETNAME_ATTRIBUTE_BATCH = "batch"; +enum CRED_TARGETNAME_ATTRIBUTE_INTERACTIVE = "interactive"; +enum CRED_TARGETNAME_ATTRIBUTE_SERVICE = "service"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORK = "network"; +enum CRED_TARGETNAME_ATTRIBUTE_NETWORKCLEARTEXT = "networkcleartext"; +enum CRED_TARGETNAME_ATTRIBUTE_REMOTEINTERACTIVE = "remoteinteractive"; +enum CRED_TARGETNAME_ATTRIBUTE_CACHEDINTERACTIVE = "cachedinteractive"; +enum CRED_LOGON_TYPES_MASK = 0x0000f000; +enum CRED_TI_SERVER_FORMAT_UNKNOWN = 0x00000001; +enum CRED_TI_DOMAIN_FORMAT_UNKNOWN = 0x00000002; +enum CRED_TI_ONLY_PASSWORD_REQUIRED = 0x00000004; +enum CRED_TI_USERNAME_TARGET = 0x00000008; +enum CRED_TI_CREATE_EXPLICIT_CRED = 0x00000010; +enum CRED_TI_WORKGROUP_MEMBER = 0x00000020; +enum CRED_TI_DNSTREE_IS_DFS_SERVER = 0x00000040; +enum CRED_TI_VALID_FLAGS = 0x0000f07f; +enum CERT_HASH_LENGTH = 0x00000014; +enum CREDUI_MAX_MESSAGE_LENGTH = 0x00000400; +enum CREDUI_MAX_CAPTION_LENGTH = 0x00000080; +enum CREDUI_MAX_GENERIC_TARGET_LENGTH = 0x00007fff; +enum CREDUI_MAX_DOMAIN_TARGET_LENGTH = 0x00000151; +enum CREDUI_MAX_USERNAME_LENGTH = 0x00000201; +enum CREDUIWIN_IGNORE_CLOUDAUTHORITY_NAME = 0x00040000; +enum CREDUIWIN_DOWNLEVEL_HELLO_AS_SMART_CARD = 0x80000000; +enum CRED_PRESERVE_CREDENTIAL_BLOB = 0x00000001; +enum CRED_CACHE_TARGET_INFORMATION = 0x00000001; +enum CRED_ALLOW_NAME_RESOLUTION = 0x00000001; +enum CRED_PROTECT_AS_SELF = 0x00000001; +enum CRED_PROTECT_TO_SYSTEM = 0x00000002; +enum CRED_UNPROTECT_AS_SELF = 0x00000001; +enum CRED_UNPROTECT_ALLOW_TO_SYSTEM = 0x00000002; +enum SCARD_SCOPE_TERMINAL = 0x00000001; +enum SCARD_ALL_READERS = "SCard$AllReaders\000"; +enum SCARD_DEFAULT_READERS = "SCard$DefaultReaders\000"; +enum SCARD_LOCAL_READERS = "SCard$LocalReaders\000"; +enum SCARD_SYSTEM_READERS = "SCard$SystemReaders\000"; +enum SCARD_PROVIDER_PRIMARY = 0x00000001; +enum SCARD_PROVIDER_CSP = 0x00000002; +enum SCARD_PROVIDER_KSP = 0x00000003; +enum SCARD_STATE_UNPOWERED = 0x00000400; +enum SCARD_SHARE_EXCLUSIVE = 0x00000001; +enum SCARD_SHARE_SHARED = 0x00000002; +enum SCARD_SHARE_DIRECT = 0x00000003; +enum SCARD_LEAVE_CARD = 0x00000000; +enum SCARD_RESET_CARD = 0x00000001; +enum SCARD_UNPOWER_CARD = 0x00000002; +enum SCARD_EJECT_CARD = 0x00000003; +enum SC_DLG_MINIMAL_UI = 0x00000001; +enum SC_DLG_NO_UI = 0x00000002; +enum SC_DLG_FORCE_UI = 0x00000004; +enum SCERR_NOCARDNAME = 0x00004000; +enum SCERR_NOGUIDS = 0x00008000; +enum SCARD_AUDIT_CHV_FAILURE = 0x00000000; +enum SCARD_AUDIT_CHV_SUCCESS = 0x00000001; +enum CREDSSP_NAME = "CREDSSP"; +enum TS_SSP_NAME_A = "TSSSP"; +enum TS_SSP_NAME = "TSSSP"; +enum szOID_TS_KP_TS_SERVER_AUTH = "1.3.6.1.4.1.311.54.1.2"; +enum CREDSSP_SERVER_AUTH_NEGOTIATE = 0x00000001; +enum CREDSSP_SERVER_AUTH_CERTIFICATE = 0x00000002; +enum CREDSSP_SERVER_AUTH_LOOPBACK = 0x00000004; +enum SECPKG_ALT_ATTR = 0x80000000; +enum SECPKG_ATTR_C_FULL_IDENT_TOKEN = 0x80000085; +enum CREDSSP_CRED_EX_VERSION = 0x00000000; +enum CREDSSP_FLAG_REDIRECT = 0x00000001; +alias KeyCredentialManagerOperationErrorStates = int; +enum : int +{ + KeyCredentialManagerOperationErrorStateNone = 0x00000000, + KeyCredentialManagerOperationErrorStateDeviceJoinFailure = 0x00000001, + KeyCredentialManagerOperationErrorStateTokenFailure = 0x00000002, + KeyCredentialManagerOperationErrorStateCertificateFailure = 0x00000004, + KeyCredentialManagerOperationErrorStateRemoteSessionFailure = 0x00000008, + KeyCredentialManagerOperationErrorStatePolicyFailure = 0x00000010, + KeyCredentialManagerOperationErrorStateHardwareFailure = 0x00000020, + KeyCredentialManagerOperationErrorStatePinExistsFailure = 0x00000040, +} + +alias KeyCredentialManagerOperationType = int; +enum : int +{ + KeyCredentialManagerProvisioning = 0x00000000, + KeyCredentialManagerPinChange = 0x00000001, + KeyCredentialManagerPinReset = 0x00000002, +} + +struct KeyCredentialManagerInfo +{ + GUID containerId; +} +struct SecHandle +{ + ulong dwLower; + ulong dwUpper; +} +struct CREDENTIAL_ATTRIBUTEA +{ + PSTR Keyword; + uint Flags; + uint ValueSize; + ubyte* Value; +} +struct CREDENTIAL_ATTRIBUTEW +{ + PWSTR Keyword; + uint Flags; + uint ValueSize; + ubyte* Value; +} +struct CREDENTIALA +{ + CRED_FLAGS Flags; + CRED_TYPE Type; + PSTR TargetName; + PSTR Comment; + FILETIME LastWritten; + uint CredentialBlobSize; + ubyte* CredentialBlob; + CRED_PERSIST Persist; + uint AttributeCount; + CREDENTIAL_ATTRIBUTEA* Attributes; + PSTR TargetAlias; + PSTR UserName; +} +struct CREDENTIALW +{ + CRED_FLAGS Flags; + CRED_TYPE Type; + PWSTR TargetName; + PWSTR Comment; + FILETIME LastWritten; + uint CredentialBlobSize; + ubyte* CredentialBlob; + CRED_PERSIST Persist; + uint AttributeCount; + CREDENTIAL_ATTRIBUTEW* Attributes; + PWSTR TargetAlias; + PWSTR UserName; +} +struct CREDENTIAL_TARGET_INFORMATIONA +{ + PSTR TargetName; + PSTR NetbiosServerName; + PSTR DnsServerName; + PSTR NetbiosDomainName; + PSTR DnsDomainName; + PSTR DnsTreeName; + PSTR PackageName; + uint Flags; + uint CredTypeCount; + uint* CredTypes; +} +struct CREDENTIAL_TARGET_INFORMATIONW +{ + PWSTR TargetName; + PWSTR NetbiosServerName; + PWSTR DnsServerName; + PWSTR NetbiosDomainName; + PWSTR DnsDomainName; + PWSTR DnsTreeName; + PWSTR PackageName; + uint Flags; + uint CredTypeCount; + uint* CredTypes; +} +struct CERT_CREDENTIAL_INFO +{ + uint cbSize; + ubyte[20] rgbHashOfCert; +} +struct USERNAME_TARGET_CREDENTIAL_INFO +{ + PWSTR UserName; +} +struct BINARY_BLOB_CREDENTIAL_INFO +{ + uint cbBlob; + ubyte* pbBlob; +} +alias CRED_MARSHAL_TYPE = int; +enum : int +{ + CertCredential = 0x00000001, + UsernameTargetCredential = 0x00000002, + BinaryBlobCredential = 0x00000003, + UsernameForPackedCredentials = 0x00000004, + BinaryBlobForSystem = 0x00000005, +} + +alias CRED_PROTECTION_TYPE = int; +enum : int +{ + CredUnprotected = 0x00000000, + CredUserProtection = 0x00000001, + CredTrustedProtection = 0x00000002, + CredForSystemProtection = 0x00000003, +} + +struct CREDUI_INFOA +{ + uint cbSize; + HWND hwndParent; + const(char)* pszMessageText; + const(char)* pszCaptionText; + HBITMAP hbmBanner; +} +struct CREDUI_INFOW +{ + uint cbSize; + HWND hwndParent; + const(wchar)* pszMessageText; + const(wchar)* pszCaptionText; + HBITMAP hbmBanner; +} +struct SCARD_IO_REQUEST +{ + uint dwProtocol; + uint cbPciLength; +} +struct SCARD_T0_COMMAND +{ + ubyte bCla; + ubyte bIns; + ubyte bP1; + ubyte bP2; + ubyte bP3; +} +struct SCARD_T0_REQUEST +{ + SCARD_IO_REQUEST ioRequest; + ubyte bSw1; + ubyte bSw2; + union + { + SCARD_T0_COMMAND CmdBytes; + ubyte[5] rgbHeader; + } +} +struct SCARD_T1_REQUEST +{ + SCARD_IO_REQUEST ioRequest; +} +struct SCARD_READERSTATEA +{ + const(char)* szReader; + void* pvUserData; + SCARD_STATE dwCurrentState; + SCARD_STATE dwEventState; + uint cbAtr; + ubyte[36] rgbAtr; +} +struct SCARD_READERSTATEW +{ + const(wchar)* szReader; + void* pvUserData; + SCARD_STATE dwCurrentState; + SCARD_STATE dwEventState; + uint cbAtr; + ubyte[36] rgbAtr; +} +struct SCARD_ATRMASK +{ + uint cbAtr; + ubyte[36] rgbAtr; + ubyte[36] rgbMask; +} +alias LPOCNCONNPROCA = ulong function(ulong, PSTR, PSTR, void*); +alias LPOCNCONNPROCW = ulong function(ulong, PWSTR, PWSTR, void*); +alias LPOCNCHKPROC = BOOL function(ulong, ulong, void*); +alias LPOCNDSCPROC = void function(ulong, ulong, void*); +struct OPENCARD_SEARCH_CRITERIAA +{ + uint dwStructSize; + PSTR lpstrGroupNames; + uint nMaxGroupNames; + const(GUID)* rgguidInterfaces; + uint cguidInterfaces; + PSTR lpstrCardNames; + uint nMaxCardNames; + LPOCNCHKPROC lpfnCheck; + LPOCNCONNPROCA lpfnConnect; + LPOCNDSCPROC lpfnDisconnect; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; +} +struct OPENCARD_SEARCH_CRITERIAW +{ + uint dwStructSize; + PWSTR lpstrGroupNames; + uint nMaxGroupNames; + const(GUID)* rgguidInterfaces; + uint cguidInterfaces; + PWSTR lpstrCardNames; + uint nMaxCardNames; + LPOCNCHKPROC lpfnCheck; + LPOCNCONNPROCW lpfnConnect; + LPOCNDSCPROC lpfnDisconnect; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; +} +struct OPENCARDNAME_EXA +{ + uint dwStructSize; + ulong hSCardContext; + HWND hwndOwner; + uint dwFlags; + const(char)* lpstrTitle; + const(char)* lpstrSearchDesc; + HICON hIcon; + OPENCARD_SEARCH_CRITERIAA* pOpenCardSearchCriteria; + LPOCNCONNPROCA lpfnConnect; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; + PSTR lpstrRdr; + uint nMaxRdr; + PSTR lpstrCard; + uint nMaxCard; + uint dwActiveProtocol; + ulong hCardHandle; +} +struct OPENCARDNAME_EXW +{ + uint dwStructSize; + ulong hSCardContext; + HWND hwndOwner; + uint dwFlags; + const(wchar)* lpstrTitle; + const(wchar)* lpstrSearchDesc; + HICON hIcon; + OPENCARD_SEARCH_CRITERIAW* pOpenCardSearchCriteria; + LPOCNCONNPROCW lpfnConnect; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; + PWSTR lpstrRdr; + uint nMaxRdr; + PWSTR lpstrCard; + uint nMaxCard; + uint dwActiveProtocol; + ulong hCardHandle; +} +alias READER_SEL_REQUEST_MATCH_TYPE = int; +enum : int +{ + RSR_MATCH_TYPE_READER_AND_CONTAINER = 0x00000001, + RSR_MATCH_TYPE_SERIAL_NUMBER = 0x00000002, + RSR_MATCH_TYPE_ALL_CARDS = 0x00000003, +} + +struct READER_SEL_REQUEST +{ + uint dwShareMode; + uint dwPreferredProtocols; + READER_SEL_REQUEST_MATCH_TYPE MatchType; + union + { + struct _ReaderAndContainerParameter_e__Struct + { + uint cbReaderNameOffset; + uint cchReaderNameLength; + uint cbContainerNameOffset; + uint cchContainerNameLength; + uint dwDesiredCardModuleVersion; + uint dwCspFlags; + } + struct _SerialNumberParameter_e__Struct + { + uint cbSerialNumberOffset; + uint cbSerialNumberLength; + uint dwDesiredCardModuleVersion; + } + } +} +struct READER_SEL_RESPONSE +{ + uint cbReaderNameOffset; + uint cchReaderNameLength; + uint cbCardNameOffset; + uint cchCardNameLength; +} +struct OPENCARDNAMEA +{ + uint dwStructSize; + HWND hwndOwner; + ulong hSCardContext; + PSTR lpstrGroupNames; + uint nMaxGroupNames; + PSTR lpstrCardNames; + uint nMaxCardNames; + const(GUID)* rgguidInterfaces; + uint cguidInterfaces; + PSTR lpstrRdr; + uint nMaxRdr; + PSTR lpstrCard; + uint nMaxCard; + const(char)* lpstrTitle; + uint dwFlags; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; + uint dwActiveProtocol; + LPOCNCONNPROCA lpfnConnect; + LPOCNCHKPROC lpfnCheck; + LPOCNDSCPROC lpfnDisconnect; + ulong hCardHandle; +} +struct OPENCARDNAMEW +{ + uint dwStructSize; + HWND hwndOwner; + ulong hSCardContext; + PWSTR lpstrGroupNames; + uint nMaxGroupNames; + PWSTR lpstrCardNames; + uint nMaxCardNames; + const(GUID)* rgguidInterfaces; + uint cguidInterfaces; + PWSTR lpstrRdr; + uint nMaxRdr; + PWSTR lpstrCard; + uint nMaxCard; + const(wchar)* lpstrTitle; + uint dwFlags; + void* pvUserData; + uint dwShareMode; + uint dwPreferredProtocols; + uint dwActiveProtocol; + LPOCNCONNPROCW lpfnConnect; + LPOCNCHKPROC lpfnCheck; + LPOCNDSCPROC lpfnDisconnect; + ulong hCardHandle; +} +struct SecPkgContext_ClientCreds +{ + uint AuthBufferLen; + ubyte* AuthBuffer; +} +alias CREDSPP_SUBMIT_TYPE = int; +enum : int +{ + CredsspPasswordCreds = 0x00000002, + CredsspSchannelCreds = 0x00000004, + CredsspCertificateCreds = 0x0000000d, + CredsspSubmitBufferBoth = 0x00000032, + CredsspSubmitBufferBothOld = 0x00000033, + CredsspCredEx = 0x00000064, +} + +struct CREDSSP_CRED +{ + CREDSPP_SUBMIT_TYPE Type; + void* pSchannelCred; + void* pSpnegoCred; +} +struct CREDSSP_CRED_EX +{ + CREDSPP_SUBMIT_TYPE Type; + uint Version; + uint Flags; + uint Reserved; + CREDSSP_CRED Cred; +} diff --git a/keyring/source/windowskeyring.d b/keyring/source/windowskeyring.d new file mode 100644 index 0000000..eb07392 --- /dev/null +++ b/keyring/source/windowskeyring.d @@ -0,0 +1,74 @@ +module windowskeyring; + +import core.sys.windows.winbase; +import core.sys.windows.windef; +import windows.win32.security.credentials; + +import std.format; + +import slf4d; + +import keyring; + +version (Windows): + +class WindowsKeyring : KeyringImplementation +{ + this() + { + } + + static WindowsKeyring create() + { + return new WindowsKeyring; + } + + private static wchar* targetName() + { + DWORD length; + GetUserNameW(null, &length); + wchar[] username = new wchar[](length); + GetUserNameW(username.ptr, &length); + + return cast(wchar*) format!"%s.sideloader-account\0"w(username[0..$ - 1]).ptr; + } + + void store(string account) + { + CREDENTIALW cred = { + Comment: cast(wchar*) "Sideloader\0"w.ptr, + CredentialBlobSize: cast(DWORD) account.length, + CredentialBlob: cast(ubyte*) account.ptr, + Type: CRED_TYPE_GENERIC, + TargetName: targetName(), + Persist: CRED_PERSIST_ENTERPRISE + }; + + auto result = CredWriteW(&cred, 0); + if (!result) + { + getLogger.error("Cannot save the account in the Windows Credential Manager."); + } + } + + string lookup() + { + CREDENTIALW cred; + auto result = CredReadW(targetName(), CRED_TYPE_GENERIC, 0, &cred); + if (!result) + { + handler(null); + return null; + } + scope(exit) CredFree(cred); + return cast(string) cred.CredentialBlob[0..cred.CredentialBlobSize]; + } + + void clear() + { + if (!CredDeleteW(targetName(), CRED_TYPE_GENERIC, 0)) + { + getLogger.warn("Cannot delete the account from the Windows Credential Manager."); + } + } +} diff --git a/screenshots/screenshot-gtk-2023-11-28.png b/screenshots/screenshot-gtk-2023-11-28.png new file mode 100644 index 0000000000000000000000000000000000000000..1d3d60f86ecac6cdd24fc047cdbac9ab53defca9 GIT binary patch literal 13326 zcmeHuXH-+^*Y9x{Wz<28phy=`Q0dY;gAtG_f`U{fNG}FLCxGJ!p(6;PqjW)F=pA$f zB3%-CC?k;)dhdkyIlgzT`{91LYrS9YT6g_jx{&1LoTu!)e`P-jd!VPmaE|R91VIe< zHIWY?=ocdRckZ_{;FSyX1@qv?uO4dmjeY}v0)Df>LC`hmKJpJEpS0x(-?wJl*uCQz zb%~5Jg6${mPqEflnBJWsseVP3KQR5O-sxDrcJweVL{j*^jwYETc`Vfv=ohHF$svaE zPnnhcCY1J|HD56*GWsKDtT6KL9vft+cLgO$2E`nWJ1BC>KJ{sYteJZ z5R{bkDX_ztkuXX-I0fZ74uxzxpYIsm@jC_Cj(t44IC?R$;=9x@P>z9_hNpsttwM`5 z6#ST&_{W56*{EwNJ@js(QrDgEelNrqpFP_)R=1O5Bv1U z(m^_z)U-0!SYxQwMTrisiC z?wrt5>Dxwi%Kd?ZD{e(edQLa>o^yH>zcFuC>YzZ-*q}|n`7+Ja+W+=qsUcs~%OuIH zMTYO{tsZPs`cUvg5@*?9mb?Q_fRhlZZMJvg_`$zOiJpY&M`Y#EO`U3PNL4Vk^G>wi zD|zY?#?e~d#lWP`XCZ1{>y*+cD&M_R5iIFruKFsGnF8hk5-Ulq{!c$Xzk0g*hm!f! z<5Q4*h*rfwRkz|W`+zVD$>D69`aR6P!(!oS&$t`B@%Ov@&qHGAi*E!{KZvwg1hO?% zhfyV4E%|us16smCmsErdtyvc4(d`0Ir$1%3Wd(4H{LBK_n(36y{pt;H2slY zTCP;MB$1rghN++{%P3vTaPDYBA}q93^(!;RA|)X)vJP|O{yDPXX-{HOiCq}sYGre}tJ?x~bCpvE< z?KUs=L8=WY^G4nYS+zb5iUm$#RMR1yeEB>Q7css#qU1_H=j9#2rlo?l5y!;>1^%n_ z;QCI&%Zl~{-dZ1QR#J3$CsaAui^|un?h7&W3Egx!RH&=%^^+ zEyBYHRt9QsazJ^(st{+@r17mR!HHpmYAf*+fpb*iG6YrJcM!wVY;K#2Vs-MC{MKpH z+tKHC@4Y4)P_XxRQm;sG5)BQO8@~;TIEBqSk3Q$$X%Vmxby>t63~L^W@qz^2M%p(2 z5MV*@A;j4Z8w%El)!o|oHd&D-A+GEZ_1y0k%Xh3pP(HVbSBW!S(w(bC)j?I;#6jMA ziFOT)F%lzcR=qUe+}vCtub^O++F_%I!x>-4@<5QVtJC`jKF%1-7R{o;>-fdyY>)oE z)(1;53)V=x>6uR@S4ap-c3pvDQqet(Lu7utPH)Cg#m8*oMwFi(%Gf1YU87NvlqNhX z?KWEeoFHUcIZuZ`fB*VbGcqy~shk-T(44UimS8zMJDX2lUOua+NU*-Xo|o5kq8WRL zmvNhHyv2KFdS-^Z&c}DQo){OawOGu@ui|3)VE}tydrYmK_|bjct7?Wlq4ysHhaL~K zmpnTKARY6}+k1D8o}M0MZ(q=hKMjc~V7$FG^!4?BXJ9xU{KBXb;ItG&CE`RZ zMNd?%eN70zl6eb5F-9Yn9g%-wy!bPI+^7n{G$AaS^{Ju1Mbs3jZ*?XJp1iB5*epFb zJVe>pq-=kX{Gr@7mWKQB)S_NjGOH*J9haR zKF?FDM|8;-rstIeWNyrGaFzvOx5h6EJ~kg89Nca>JUkTs4nYNfynle}@9&>qSqYrT zx)+f_u67|am8s+v^;}~qWg&muy8Aln4qtzIUTnz@yJoK2tu%W%js3XjX(0@CTTX+^ z_s0g<%pgQR1a049KKG_;#(J{Rw}m`7SmiogVj~?d6M6+$hlD_jt*8jHD!Uvd-+EKVKbb z!BlTG$no9|a>5wZa$a_2;kLSe`%gc9H9vVv`zph!gh?xErB6ble>Zh;QK5_eJis4U z8=LC~2M4|3I5~rT=GMVPYGTUA?KpcM=ltjD^^X&1x1MIXB+$ev06^xScYuOFu(Ps; zm6Vi_P)E*~+<4WumU`92H*(~17Wad#_p3Kbw?&k0SW3rtaIDgWK#(E6Z;lWA!Y!J~ zS&X?NKRqL&?Wff+(C*3B%-cAYYS^{YF*Iy+==H_;HwWlxy-md^*q~@Rk6EOhogD_V zwE8tbnH$8hxS}G*-Me=s{Ni3EvyrrQx5Eo2Ee3lfEEbcM7ABNYY@EdHqch^8%}hNR z#-^>7f#VyV^5FS#J+|33H8OsFezzy5`uhTb z=qS@!2>+=tJyUw2G$$k2)!sS(nR=haN6>8Cr_XaOTA1b~(087%5CxE^Tx`H>G`r8z7U5#wrfnX*jJHs4L2aP(Tb~owNsG64JW%Q))T-(M>7m7;E zE%RI)^7-*bV|q5#Et>o2U;5@i4rokMIyIg6(XHB7X(g*cp zbH8?rsLd|U0oiNEsL*_}2%O&IXp&;wv7f&`yLhBIAZjt> zo^MgNroiCexaReCcGUNOUaxPF^a&~_n<|8upd*S8ZGVoEgo+LuZd&nNf6s`$`MAWK zRb~QX_|dXSUQcOzK>hdAP!i9mbM->9SYB3szQFnu38HM?oe$=Z#4N^clT{XW8n{0z3z zpyS1Std5jpST1|K!)z#nQ`D5Y2;u{oJh@YCQf7V6nOrR_(sHzcMp!uRu1u`Ii3zwa z?Ka6FXk6ml`Tz=Mm9*Dw`HRn|5s3BOpp#uDTA~*vO2)MJ6y%aqQGp(PyEPK8w4zV@ zp+7z5R2qefQIZ}iv0#6##*-*&#nq>@Ykow{q&eJemrSjkRxs+?|BZe2N4$PC4%e_! z@AS`cS}`4zC!)(OEfm=1#dlq4$0mkc@9FgMNrkOY9v^SD^XMgGOW)^;yW<`VtcIMO zc<;(__n6ApTbO(3FVpp4fp~eV-KLbk2lj$7<$MMh1#lv2f{TkQ`uq3q$z1IiX-h|I z1s9%lzXq@6@Ou$g+NRqgDEo7nZaXiacg4BXFCqH@&Ce<-@1NZ8FBca!t-M)MH=81R zFrQ5rLIR8xf+yrO^fCE`^|zK1eLc-#>ibZx@QPk}!Ctw!v^{uT?(eGX{rigY?$w3x z{LDbVGwSq^Was?+{9oLk8g?iB1p@0`mk568+yG_tLs5Nd{c(lGpiHYe^cAIKe8v6Kz3FOLNPQ|gV39a9qZU2v^ zj%W+&-B&YH`I81-3Wn9iM~7nyT7l)F7GBMmCZAF7F^Bx(sVXPqbWyt&XKD5++D;86 zCtfd{o;yGc4(-QBvzY*-e#SF{o6lXvqqQ$(e%p>G$X_5&Em9~$#i%o&3-)6%>}4kv z#`jxdZ!n-178fJH4c0snWrDo!2ys*Qz2|y} zOd}6~+bi71^BVUgp~_l-ey?7=A_b0r{`|Rh>RM8}xUK=otdy%KgL)(NO223{)l^ho~6AvT>%B(D}6OUV5M*3G(MYpqpfcd$et_~yEhQTKRY*}{hMtBj zk!wWnBT8l8+;Oz!pD0Lycy~A*J=dl?d1N03;M@Ls?*O$>6GgV=y|NHO1>rik+ zxje7o1#JZgO0k}5#=_{<)YQ~ZA{mlUXXxp(TUuJ4X$yd2=Ea|L3cA!e<6h>!yE4DJ z8nd#pk|bfLhw<`?#*&``vIjWLRgg{Z-@j)K85`wN@1Gm0*iiZJ$&I0g-G*vYLy8WW|pG*IH1ys$b^Ygft zy@(dv;Yx$(P^lF+TGOKbhYaQn^}!(6r8v#^@}Awd*4F|Tpk@a z@U1g=D`hYLo)oF|5mZduZp+EBZR4-dSW@hnhg1-+cILh_3FvuH;$BPv67v%w9~C`r z3spJ#UOOEwI9<}P5{|MLyrAHxa2!|u*Y-8Zs4 zml;G0#%B1h)H$%TL(EV37;-vq<)>ZBq;2IpO`aT1odo`@*UhwpMHDGd7gfWJ-RiFe zfSvDwgDxU$b6&pnojROkNl&Mo|M5-n3!0n>IccgC;%vZ>ef1#Qy^Th*=m+S#diB> z)e<;f4XP#H!s|&Ix}6nGyf1Li*w|QcX9PtVGSi=xD)rSh_xb)BeY8#>z{720v5FMP z@Zk;I`@-EW)4U$_b#7CNyC^PK8{ZDLhpXsuaX3&qiXiV5_kYB%1BTQpTr+-Ycf#AM zjZkb}cc)ircU%^{PWk)Gg$Vd#lTH2%q`&4}JjckjG~ z2Jz9lFjz#{A-l!&1@2Uof%05-*s>nw*6L)rnL-yW=e;s+Qf_lU&1=j7fX~8Sr*ZQo ztD|Wa#t~8B)gB-sne8%sZ2^n(b0){#(7v#D@BF*uBhgE-w`HaRJld9%b@=ttKJ-z^3|#R6A%1Kq}VB%5SbgH8?!_b~m(yHlAy&Jamg zlkS5%*g=BSusPFfwP*hvCyH3GWgaitQ9yzg3Fe8`&u5v`xzC&;*8oPdvcx@jw6aQb zg`=5Lja~Jhih#2Vz1p-JO{P`XG!?JhRDyw#@nnsWaikeVh`xIDs_fIl)XA;9+JFB% z-|jl)MRq|TU~KRK2|Z0vp`k&vHcva8I@u6CaUWj>!yI+q33dU>r|j96ko$yFVunJ? z(8D<}qKNIqVFH}>KF5nzR^o->lSi{Mgj~EOo7Ks|u#^0b!sL21x@L1f;Aoxu1U-)3 zop{q@i-HTwcgCRF1FZAS+cA^lgnf06YU0Xm1$^wrM{+G^!CCFcgblulMMib!$tUcQ3LUqL5 zK+h)IwY#0{xDk@=GFSmR(l7Y~Z8OM0ByjmJ)sjI0(}yR8K)_NQ+?1((`m8{j*oqfW zLMn$9D(nr%(-ET45p6SF_+iPC=`zd1vEgWPHF&m3jjIK6iIE(%7aD{+FWM*;Ex%Au zhSgJCZ$-!W9#Hd9`x`T8Kp$I+td19CH@@NZ`O31(QN09S-?+1MPd+MPgeE``Nn(O-E&Bgs39RwwndWn0_Z zZ6Ng9T?7O7N>hL!9SAtOdqM6;u!sH0f^iF*cf*65t?~NGaw0i+5QQM<&%IXL+SzGm zXjFeAan@#wty)S0OBg9 z<)Ek~yyckOQhYG(5ab3CzeY;N-WJFVa32rY?D+{%6W&vt>@u#AES5ezUMu79R{abE zz~c!@d0T>?1?~WP1LSTrDq7CgnOyj&Xj<6KS33Ys86AEmyL8XTgM{7PT>_BDgtlcs z6sP|=FB(~FRx`A#WsIjikYwk1q~+mWv&zTBS1PbpL;Dk zjpLjC$`=y}vxDZN8PS+!vs*V+XzMWq#=x~#d-$MzJj~2iL2aO@$@5M}SRGjhOfzNT z)ZSl=xFXEvJ+-#>;)tR`Lm`(`j5j~ib1SaUJo=Pm00X3e`Qd7sw~Ieu-5}F90Q_fM z-2BZf4yV8c>u_2~tp+Vu4Y!|WF&A^?m*s6QUIhvINdJTP7Y3PDc#V790yjshUtN&D zIl15K&Gq*xfHp$gG+0Iy27?)wpNHd1PkH~xUg^m}X)Vw}lrAfpdzweD9DN#_D-h(D z37c)Af#TAx<5v-f`o6mZhWc&&8r(`=LEhfpa6!JzZB~*D)>W`volP?e7H_}?aT=4! z?1K#e1oz`nF-E4B7pK5dKc*DB>=a*eK5$0RN^YgG zTfu9o;9z^GWmBPr32}(`21=7Za4VZz*JlZs2n0Lj&E!3hAo8w<`O4cL=!WE37=Hh~ zRX^x}R7h~1^{4w5-VN$FEJ{q8{5&}T3a%&CG~R%%gHY~3ACADi@94G9^aQ}era@f^ z(@m9b8*bjIYPIwHxdM%wy%CJq`Mc}zL*S|~QvO`~h!Aw70SAZpojaFWz)myD7MmLc z1qB5hkTRo717-x*GjDv`3WzNH4=?1kv{QKG7>*w9CYLr2$rkUMeHBgC*w#>w<&1 z0~buc02vdc5!XqXG)WPFBtK~%tlDpAC$^IGRXAgm59~i$G)M#U1?ZF&%^9#Rjthsb z6I2D)+(U^Q}zob^*5zc*El8FHtu>Sn#X+R6IAKaAm zzgdwH>zW0M6S&=MZ#sGDJ?K@~tc=&a>6>fA#Gj?|FD@-b0z=-Gkq)xwQBqRsAEl-t zdiZlvZ)Wzzq>r6}-f;n=ct}eoIo369TBS|aSQ3gwmhlL!+H>(t*Z`{zUP9B@5PRha zr<9yMGO8%es0sM9aHiQZzPjbN)vwWG4poL(-(@~`+KPMfx>|jh=`H)mA59R$mi{gk zp`V$XXxpW*>II_+cHqrQEt1N+3xw?R|C^?L!9|sR0YgV2$%^IG-7l9{Gsz^*#nlEG zke``7YhOX*5g+SHN8mmw+zVZ#c`ohzf!*ZewVwL)6o~|_mG)e`1+1C#-sEz+*mSUq zL}c8>6AFQsFxZ=3<~-`w+CW>4NKq1kf~#%R>wDe?-Ji!`8TLV)WzF30w#%C_k<=H# zJMCPA-nsk+1_MZ+I$am}DzGY^kRN1v#H!={YeEBtn0KD%60}&fz!z9^HSBrLEC-^FnP9Xl(ra z?O7=26(F#5ukw&OA~EdAhr^5-kv}McyI5h~of8Qf<0_})MgjD6XRp`0nF*A6w2)<^ ztOy{=wfnQFD8M@qWhz<5rItSpSePKdwi$m+;)gqY6Y8`~?D6_RfXJ1M^%m>ztS5r% z`U=-AkM1dq*&3S5&^*ZN!Vu*#C6ab>vXnM5-rCRGfK zA<;w91v)JhS^@B`32{n#QgT=)kR2j2-mt_qJrJuvv900GMuM4Ue|Mn5r^9<}|%9 zu7#%3z{{Jp*s>!!x96)N9XR_BDf%0&`I6smtTQbtya%)v{6Q|CV(ik+g%wcLm4X` zF3$1E$g^oDa$Eu%i0%e3LSfHLB6_3&4cF^^zYA2a^-KUos2Z5~01FunAiH&b-u_F* zZb5c*IKYxfe^+QM6l4Uuv?4RUX_t}hfI}ed1?J~v;?rfeGctC}#-bjs`mEvZ%#5&NtH+gv{PiVUutu)y_ z;25GQ?X`0m{#k(f!tku?=4a?U@;6UNNE(Ur-NbJWPluiWA00*{spSTnjyPFLq{KA9 zHyl7mxD5k(GEc{qrw)c{tot&QVAs6JxHKA$Iv6#7J{UOb=y5cm4XF2V9R3$oCOId1 zu|+ug*SjV91{zm~ip_vf<3u3DWo54dKcT30wZx*q8dx8c<|})!g8yG~nHf&B)09oQ>`I-PaHZm7fj@En@KIFGoFOO9TUQpiq z_f`~ZFi6u^DF7R68BgW40V@YK{brAWw&eQh>%xwaM6WDgM!-gg&%ZC}r(woRo`WR~ zSaDEh5#ltKJ{padq!L8iYJ#ALcQp;;)!u(w;`vkwPZsVKjaO^6+)Q)@6EXYgFM#er z8)&yu;c3HrP6noNUrj|A=v0#WKWSOg18&@bSE`!#nNl`u8&|kD0_dD{26kt(kRWoxi+@Pq@X%2_Iod?!_tGWlC0a;l0^9{k z2=qCH5e0x5o=m`-I0`m#97ECttdsa{ZnV+YcnaFu-Hl2AsH!Ow6RY>qTx9M%I~1G( z@4K^f$}Ct4qa)pNuxUWDpjIcFbh!{vtSzvrqkyrGX82>%XE*gDCXDduQ*g2a^ezkL z@ZkS9IB!iW5_KORX+Jo+QVA}-3cAXZlas(Rbl8}Of^FGBha80V*BN&Tuc@X?CV2Kn zQt#a6|LKAs%&|jBr#JQkBBP_*!AgM1-UGg(4nJR(p{8a&(m{vi_dInN0l*|=2b5S=9rh_U=6CIl>LosnO6}dEUywf(Uda=GT6!gJz z;{JC_EVeo}qr_7zp3}}rLu1TR9^B9O!-s0UaU`S@|;n++L)%;!#o zVKqIXjQrsFIp!=`>7rx6~7G*4t`yygSyWF z^ae$JLlqg>Soxu{Nh{SkE3@E56Y+VWq(qJr_HJA=Iw6qKq0u@II+a1ZKc4~)aRGO2 zKOT2A3NIickm6hNDBBF(NtEZaC?uvz^s`Hkx+&HHXMNWAwc7U`pb@EDg3zaruzG}F z`1y`_paTIZ(U&kl_Wux@7n`xvlQK^u2HnBk9@Y*D0bMD-yMRc6Q#e2P&Ok}I<)hBv zh)XLVMQ%9U`vPo-I=W6n%Fy8-*1`Gk?g8JRyyz~S1TqsidNWTt;a`C+GoCi_1|B1P~; z&w%c$&K4DN)~-@kssiTU=&1W?5Ud&v3<5@mT zyqF%P=o59$!0g?XUBRZva+lo0S<1`}&V9CwY>ZN$y%D1)6zug_7$b;v!c_T)Fw7Dc z2Q#t*%#QeRZ$Uh_KHput7E_z)-C*= z9E_-q2F^$jbGtOPGT}qlMk#+}IEQKEf1stTP3zU%1U7dU1F&RyK)*BK&Z(Rjd z7B#3W6>obLjpZpC`(;j8VQ_T1VC-RJSZ|{N7dX;sS5cInkexU>Zv$s^d;y&q4lt<$*J(~Dc zybYKKiSkIE+q25U0KZ}!*c8A4&U?O7e7t$N1e}uHPnp*`)UC9wH>l{}Uc!&)fFm33 zAe4152Dyx!S7y3aMAH*QR;Cc5nWNcb=$})3^k^~d=+_#7!w1i*7+D{O&#NcBVO^>e zS;iFvs7DiBa`Wp1gLL-kFJ%9-tfj3Qe)vP6R*4nO0OdSVdMVdW|Er+zRFY6(ky4OS zMgNC8@%=IuT?(D0#`01YxzD+oTr!tly`H`bCAr+b=aS2K_OV%>LDxo%cq~|H^*KYf_ zo&3f4ZY}&|hV$e4u^m(8k0#50{J;hhJ2Wzj6E{%ByQVr1`Ie8)Z(P$GbjBd%?Q#T{ zWu7c0_Oaq>rCcSDsL{c5j?r*4i~5gF{MJ^1OvZW`|nckcn&9=sa#K6qAdM zZ^tz4>Q{mz>#L zwQM-yJ(kPEyvTW%n3Un1*^yPB2)@%``lQ$@`eC2xT+ZQu^-w6Lb*l|S#gY*1ByqC~ zP^>gbYSGNiXP9X&-r=C;THY-h8-i5D+&YeVO=ErAltt#P_}!_88B*Z+4BJ zRi7`>#)O*zS}X4ux^FzzLfj79(RN?T(=rp{=j_9D%gZd3f$Yl3V4eG*DD80ZJoT*P zojmQ`^xol&_FV+@l$XSsW0+i4vg`YhulST~0{VupGl4mS5q$BWQ^2R&NKN(;IAc2* z8=bVDBogGDG6iH!vSh*x*9;+vN|4qWL!k>PsnNsuo!#G{cLsTUPxJjEjnpwnC#1D0 zs`m1v0u*d2$@`de@jJtymbk11$|ZOBcj%i|o?w6>=eK*sR}y+FPeHNDBWN-7FfsNK+J+0euVYAI<0T+sV{w6~N2e7U1iR}r7WP$tK2$p@*j zRXtZ_>8bvj=2FT6JLXeL0P>6@zcN6wiCh*09 w-)8Us)=HgIIt>xEIOzU|-+uuglUO~Wf2sXouxP|1628m*dwR$cwWojn7i#bY`v3p{ literal 0 HcmV?d00001 diff --git a/source/server/appleaccount.d b/source/server/appleaccount.d index 8aaf521..e9beedb 100644 --- a/source/server/appleaccount.d +++ b/source/server/appleaccount.d @@ -5,8 +5,10 @@ import std.array; import std.base64; import std.datetime; import std.datetime.systime; +import std.format; import std.sumtype; import std.typecons; +import std.uni; import std.zlib; import botan.block.aes; @@ -39,6 +41,7 @@ enum AppleLoginErrorCode { mismatchedSRP = 1, misformattedEncryptedToken = 2, no2FAAttempt = 3, + unsupportedNextStep = 4, accountLocked = -20209, invalidValidationCode = -21669, invalidPassword = -22406, @@ -55,10 +58,12 @@ struct AppleLoginError { alias AppleLoginResponse = SumType!(AppleAccount, AppleLoginError); struct Success {} -alias AppleTFAResponse = SumType!(Success, AppleLoginError); +struct ReloginNeeded {} +alias AppleSecondaryActionResponse = SumType!(Success, ReloginNeeded, AppleLoginError); alias Send2FADelegate = bool delegate(); -alias Submit2FADelegate = AppleTFAResponse delegate(string code); +alias Submit2FADelegate = AppleSecondaryActionResponse delegate(string code); alias TFAHandlerDelegate = void delegate(Send2FADelegate send, Submit2FADelegate submit); +alias NextLoginStepHandler = AppleSecondaryActionResponse delegate(string identityToken, string[string] urlBag, string urlKey, bool canIgnore); enum RINFO = "17106176"; @@ -78,7 +83,7 @@ package class AppleAccount { return appleIdentifier; } - private this(Device device, ADI adi, ApplicationInformation appInfo, string[string] urlBag, string appleId, string adsid, string token) { + package this(Device device, ADI adi, ApplicationInformation appInfo, string[string] urlBag, string appleId, string adsid, string token) { this.device = device; this.adi = adi; this.appInfo = appInfo; @@ -90,8 +95,82 @@ package class AppleAccount { package static AppleLoginResponse login(ApplicationInformation applicationInformation, Device device, ADI adi, string appleId, string password, TFAHandlerDelegate tfaHandler) { auto log = getLogger(); + return login(applicationInformation, device, adi, appleId, password, (string identityToken, string[string] urls, string urlBagKey, bool canIgnore) { + if (urlBagKey == "repair") { + log.info("Apple tells us that your account is broken. We don't care."); + return AppleSecondaryActionResponse(Success()); + } + + if (urlBagKey != "trustedDeviceSecondaryAuth") { + string error = format!`Unsupported next authentication step: "%s"`(urlBagKey); + if (!canIgnore) { + log.error(error); + return AppleSecondaryActionResponse(AppleLoginError(AppleLoginErrorCode.unsupportedNextStep, error)); + } else { + log.warn(error); + return AppleSecondaryActionResponse(Success()); + } + } + + log.debug_("2FA with trusted device needed."); + // 2FA is needed + auto otp = adi.requestOTP(-2); + auto time = Clock.currTime(); + + Request request = Request(); + request.sslSetVerifyPeer(false); // FIXME: SSL pin + + request.addHeaders(cast(string[string]) [ + "X-Apple-I-MD": Base64.encode(otp.oneTimePassword), + "X-Apple-I-MD-M": Base64.encode(otp.machineIdentifier), + "X-Apple-I-MD-RINFO": "17106176", + + "X-Apple-I-Client-Time": time.stripMilliseconds().toISOExtString(), + "X-Apple-Locale": locale(), + "X-Apple-I-TimeZone": time.timezone.dstName, + + "X-Apple-Identity-Token": identityToken, + + "X-Mme-Client-Info": device.serverFriendlyDescription, + + "User-Agent": applicationInformation.applicationName + ]); + + // sends code to the trusted devices + bool delegate() sendCode = () { + auto res = request.get(urls["trustedDeviceSecondaryAuth"]); + return res.code == 200; + }; + + // submits the given code to Apple servers + AppleSecondaryActionResponse response = AppleSecondaryActionResponse(AppleLoginError(AppleLoginErrorCode.no2FAAttempt, "2FA has not been completed.")); + AppleSecondaryActionResponse delegate(string) submitCode = (string code) { + request.headers["security-code"] = code; + auto codeValidationPlist = Plist.fromXml(request.get(urls["validateCode"]).responseBody().data!string()).dict(); + log.traceF!"2FA response: %s"(codeValidationPlist.toXml()); + auto resultCode = codeValidationPlist["ec"].uinteger().native(); + + if (resultCode == 0) { + response = AppleSecondaryActionResponse(ReloginNeeded()); + } else { + response = AppleSecondaryActionResponse(AppleLoginError(cast(AppleLoginErrorCode) resultCode, codeValidationPlist["em"].str().native())); + } + + return response; + }; + + tfaHandler(sendCode, submitCode); + return response; + }); + } + + package static AppleLoginResponse login(ApplicationInformation applicationInformation, Device device, ADI adi, string appleId, string password, NextLoginStepHandler nextStepHandler) { + auto log = getLogger(); log.info("Logging in..."); + + appleId = appleId.toLower(); + Request request = Request(); request.sslSetVerifyPeer(false); // FIXME: SSL pin @@ -226,57 +305,16 @@ package class AppleAccount { string idmsToken = serverProvidedData["GsIdmsToken"].str().native(); string adsid = serverProvidedData["adsid"].str().native(); - auto authenticationNextStep = "au" in status2; - if (authenticationNextStep && authenticationNextStep.str().native() == "trustedDeviceSecondaryAuth") { - log.debug_("2FA with trused device needed."); - // 2FA is needed - auto otp = adi.requestOTP(-2); - auto time = Clock.currTime(); - string identityToken = Base64.encode(cast(ubyte[]) (adsid ~ ":" ~ idmsToken)); - - request.addHeaders(cast(string[string]) [ - "X-Apple-I-MD": Base64.encode(otp.oneTimePassword), - "X-Apple-I-MD-M": Base64.encode(otp.machineIdentifier), - "X-Apple-I-MD-RINFO": "17106176", + auto hsc = status2["hsc"].uinteger().native(); + auto sk = "sk" in serverProvidedData; + auto c = "c" in serverProvidedData; - "X-Apple-I-Client-Time": time.stripMilliseconds().toISOExtString(), - "X-Apple-Locale": locale(), - "X-Apple-I-TimeZone": time.timezone.dstName, + bool canIgnore = sk && c; - "X-Apple-Identity-Token": identityToken - ]); + AppleLoginResponse completeAuthentication() { + auto log = getLogger(); - // sends code to the trusted devices - bool delegate() sendCode = () { - auto res = request.get(urls["trustedDeviceSecondaryAuth"]); - return res.code == 200; - }; - - // submits the given code to Apple servers - AppleTFAResponse response = AppleTFAResponse(AppleLoginError(AppleLoginErrorCode.no2FAAttempt, "2FA has not been completed.")); - AppleTFAResponse delegate(string) submitCode = (string code) { - request.headers["security-code"] = code; - auto codeValidationPlist = Plist.fromXml(request.get(urls["validateCode"]).responseBody().data!string()).dict(); - log.traceF!"2FA response: %s"(codeValidationPlist.toXml()); - auto resultCode = codeValidationPlist["ec"].uinteger().native(); - - if (resultCode == 0) { - response = AppleTFAResponse(Success()); - } else { - response = AppleTFAResponse(AppleLoginError(cast(AppleLoginErrorCode) resultCode, codeValidationPlist["em"].str().native())); - } - - return response; - }; - - tfaHandler(sendCode, submitCode); - - return response.match!( - (AppleLoginError error) => AppleLoginResponse(error), - (Success) => login(applicationInformation, device, adi, appleId, password, tfaHandler), - ); - } else { - log.debug_("No 2FA required now."); + log.debug_("Completing authentication..."); ubyte[] sessionKey = serverProvidedData["sk"].data().native(); ubyte[] c = serverProvidedData["c"].data().native(); @@ -337,6 +375,30 @@ package class AppleAccount { return AppleLoginResponse(new AppleAccount(device, adi, applicationInformation, urls, appleId, adsid, token)); } + + switch (hsc) { + case 409: /+ secondaryActionRequired +/ + auto secondaryActionKey = status2["au"].str().native(); + string identityToken = Base64.encode(cast(ubyte[]) (adsid ~ ":" ~ idmsToken)); + return nextStepHandler(identityToken, urls, secondaryActionKey, canIgnore).match!( + (AppleLoginError error) => AppleLoginResponse(error), + (ReloginNeeded _) => completeAuthentication(), + (Success _) => login(applicationInformation, device, adi, appleId, password, nextStepHandler), + ); + case 433: /+ anisetteReprovisionRequired +/ + log.errorF!"Server requested Anisette reprovision that has not been implemented yet! Here is some debug info: %s"(response2Str); + break; + case 434: /+ anisetteResyncRequired +/ + auto resyncData = status2["X-Apple-I-MD-DATA"].str().native(); + log.errorF!"Server requested Anisette resync has not been implemented yet! Here is some debug info: %s"(response2Str); + break; + case 435: /+ urlSwitchingRequired +/ + log.errorF!"URL switching has not been implemented yet! Here is some debug info: %s"(response2Str); + break; + default: break; + } + + return completeAuthentication(); } private static Plist clientProvidedData(ApplicationInformation applicationInformation, Device device, ADI adi) { diff --git a/source/server/developersession.d b/source/server/developersession.d index 6416f55..62284a5 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -9,6 +9,7 @@ import std.format; import std.meta; import std.sumtype; import std.traits; +import std.typecons; import std.uni; import std.uuid; @@ -74,27 +75,42 @@ auto unwrap(T)(T response) { } class DeveloperSession { - AppleAccount appleAccount; - alias appleAccount this; - - private this(AppleAccount appleAccount) { - this.appleAccount = appleAccount; - } - - static DeveloperLoginResponse login(Device device, ADI adi, string appleId, string password, TFAHandlerDelegate tfaHandler) { - auto log = getLogger(); - log.infoF!"Creating DeveloperSession for %s..."(appleId); - return AppleAccount.login(XcodeApplicationInformation, device, adi, appleId, password, tfaHandler).match!( - (AppleAccount appleAccount) { - log.info("DeveloperSession created successfully."); - return DeveloperLoginResponse(new DeveloperSession(appleAccount)); - }, - (AppleLoginError err) { - log.errorF!"DeveloperSession creation failed: %s"(err.description); - return DeveloperLoginResponse(err); - } - ); - } + AppleAccount appleAccount; + alias appleAccount this; + + private this(AppleAccount appleAccount) { + this.appleAccount = appleAccount; + } + + static DeveloperLoginResponse login(Device device, ADI adi, string appleId, string password, TFAHandlerDelegate tfaHandler) { + auto log = getLogger(); + log.infoF!"Creating DeveloperSession for %s..."(appleId); + return AppleAccount.login(XcodeApplicationInformation, device, adi, appleId, password, tfaHandler).match!( + (AppleAccount appleAccount) { + log.info("DeveloperSession created successfully."); + return DeveloperLoginResponse(new DeveloperSession(appleAccount)); + }, + (AppleLoginError err) { + log.errorF!"DeveloperSession creation failed: %s"(err.description); + return DeveloperLoginResponse(err); + } + ); + } + + static DeveloperLoginResponse login(Device device, ADI adi, string appleId, string password, NextLoginStepHandler nextStepHandler) { + auto log = getLogger(); + log.infoF!"Creating DeveloperSession for %s..."(appleId); + return AppleAccount.login(XcodeApplicationInformation, device, adi, appleId, password, nextStepHandler).match!( + (AppleAccount appleAccount) { + log.info("DeveloperSession created successfully."); + return DeveloperLoginResponse(new DeveloperSession(appleAccount)); + }, + (AppleLoginError err) { + log.errorF!"DeveloperSession creation failed: %s"(err.description); + return DeveloperLoginResponse(err); + } + ); + } DeveloperPortalResponse!None viewDeveloper() { alias DeveloperPortalResponse = typeof(return); diff --git a/source/sideload/certificateidentity.d b/source/sideload/certificateidentity.d index 7183a4b..aeed824 100644 --- a/source/sideload/certificateidentity.d +++ b/source/sideload/certificateidentity.d @@ -24,14 +24,20 @@ import sideload.bundle; class CertificateIdentity { RandomNumberGenerator rng = void; - RSAPrivateKey privateKey = void; - DevelopmentCertificate appleCertificateInfo = void; X509Certificate certificate = void; + RSAPrivateKey privateKey = void; string keyFile; + this(X509Certificate certificate, RSAPrivateKey privateKey) { + rng = RandomNumberGenerator.makeRng(); + this.certificate = certificate; + this.privateKey = privateKey; + } + this(string configurationPath, DeveloperSession appleAccount) { auto log = getLogger(); + scope(success) log.debug_("Certificate retrieved successfully."); string keyPath = configurationPath.buildPath("keys").buildPath(sha1Of(appleAccount.appleId).toHexString().toLower()); if (!file.exists(keyPath)) { @@ -46,10 +52,10 @@ class CertificateIdentity { auto team = teams[0]; if (file.exists(keyFile)) { - log.info("A key has already been generated"); + log.debug_("A key has already been generated"); privateKey = RSAPrivateKey(loadKey(keyFile, rng)); - log.info("Checking if any certificate online is matching the private key..."); + log.debug_("Checking if any certificate online is matching the private key..."); auto certificates = appleAccount.listAllDevelopmentCerts!iOS(team).unwrap(); auto sideloaderCertificates = certificates.find!((cert) => cert.machineName == applicationName); if (sideloaderCertificates.length != 0) { @@ -59,44 +65,33 @@ class CertificateIdentity { certContent = Vector!ubyte(cert.certContent); auto x509cert = X509Certificate(certContent, false); if (x509cert.subjectPublicKey().x509SubjectPublicKey() == ourPublicKey) { - log.info("Matching certificate found."); - appleCertificateInfo = cert; - goto certificateReady; + log.debug_("Matching certificate found."); + certificate = X509Certificate(Vector!ubyte(certContent), false); + return; } // +/ } } } else { - log.info("Generating a new RSA key"); + log.debug_("Generating a new RSA key"); privateKey = RSAPrivateKey(rng, 2048); file.write(keyFile, botan.pubkey.pkcs8.PEM_encode(privateKey)); } - { - X509CertOptions subject; - subject.country = "US"; - subject.state = "STATE"; - subject.locality = "LOCAL"; - subject.organization = "ORGANIZATION"; - subject.common_name = "CN"; + X509CertOptions subject; + subject.country = "US"; + subject.state = "STATE"; + subject.locality = "LOCAL"; + subject.organization = "ORGANIZATION"; + subject.common_name = "CN"; - auto certRequest = createCertReq(subject, privateKey.m_priv, "SHA-256", rng); + auto certRequest = createCertReq(subject, privateKey.m_priv, "SHA-256", rng); - log.info("Submitting a new certificate request to Apple..."); + log.debug_("Submitting a new certificate request to Apple..."); - auto certificateId = appleAccount.submitDevelopmentCSR!iOS(team, certRequest.PEM_encode()).unwrap(); - appleCertificateInfo = appleAccount.listAllDevelopmentCerts!iOS(team).unwrap().find!((cert) => cert.certificateId == certificateId)[0]; - } - - certificateReady: - log.info("Certificate retrieved successfully."); + auto certificateId = appleAccount.submitDevelopmentCSR!iOS(team, certRequest.PEM_encode()).unwrap(); + auto appleCertificateInfo = appleAccount.listAllDevelopmentCerts!iOS(team).unwrap().find!((cert) => cert.certificateId == certificateId)[0]; certificate = X509Certificate(Vector!ubyte(appleCertificateInfo.certContent), false); } - - import server.developersession; - void sign(Bundle bundle, ProvisioningProfile profile) { - auto executablePath = bundle.bundleDir.buildPath(bundle.appInfo["CFBundleExecutable"].str().native()); - // executablePath; - } } diff --git a/source/utils.d b/source/utils.d index e371977..2f0038c 100644 --- a/source/utils.d +++ b/source/utils.d @@ -1,6 +1,6 @@ module utils; -T orDefault(T)(T obj, T default_) { +T orDefault(T)(T obj, lazy T default_) { return obj == null ? default_ : obj; } @@ -19,3 +19,39 @@ string locale() { } return locale; } + +private struct Delegate(alias U) +{ + import std.traits; + import std.typecons; + + static if (is(typeof(&U) == delegate)) + { + enum del = &U; + } + else + { + alias del = U; + } + + typeof(del) delegate_ = del; + + extern(C) static auto assemble(Parameters!U params, void* context) + { + return (cast(Delegate*) context).delegate_(params); + } + + pragma(inline, true) + Tuple!(typeof(&assemble), void*) internalExpand() + { + return tuple(&assemble, cast(void*) &this); + } + alias expand = internalExpand.expand; + alias expand this; +} + +pragma(inline, true) +auto c(alias U)() +{ + return new Delegate!U().internalExpand; +} From 21837e40c53ec62dfa6319454066ccc7eb30a278 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 14 Dec 2023 18:33:18 +0100 Subject: [PATCH 15/36] Fix Windows and macOS CLI builds --- keyring/source/osxkeyring.d | 8 +++++++- keyring/source/windowskeyring.d | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/keyring/source/osxkeyring.d b/keyring/source/osxkeyring.d index 95444e6..7362bc5 100644 --- a/keyring/source/osxkeyring.d +++ b/keyring/source/osxkeyring.d @@ -15,7 +15,13 @@ class OSXKeyring : KeyringImplementation { } - void withAccount(void delegate(string account) handler) + string lookup() { + return null; + } + + void clear() + { + } } diff --git a/keyring/source/windowskeyring.d b/keyring/source/windowskeyring.d index eb07392..db14b22 100644 --- a/keyring/source/windowskeyring.d +++ b/keyring/source/windowskeyring.d @@ -53,11 +53,10 @@ class WindowsKeyring : KeyringImplementation string lookup() { - CREDENTIALW cred; + CREDENTIALW* cred; auto result = CredReadW(targetName(), CRED_TYPE_GENERIC, 0, &cred); if (!result) { - handler(null); return null; } scope(exit) CredFree(cred); From 21e3f43d7b0da58cba8910d87b2cdf963fc61179 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 14 Dec 2023 18:43:20 +0100 Subject: [PATCH 16/36] Fix Windows CLI build --- frontends/cli/source/cli_frontend.d | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontends/cli/source/cli_frontend.d b/frontends/cli/source/cli_frontend.d index 757498c..6533fb9 100644 --- a/frontends/cli/source/cli_frontend.d +++ b/frontends/cli/source/cli_frontend.d @@ -84,7 +84,12 @@ auto readCertificate(string path) { extern(C) char* getpass(const(char)* prompt); string readPasswordLine(string prompt) { - return fromStringz(cast(immutable) getpass(prompt.toStringz())); + version (Windows) { + write(prompt.toStringz(), " [/!\\ The password will appear in clear text in the terminal]: "); + return readln().chomp(); + } else { + return fromStringz(cast(immutable) getpass(prompt.toStringz())); + } } DeveloperSession login(Device device, ADI adi, bool interactive) { From cdd2aa076264445535838aafb76cd02b254fe737 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sun, 17 Dec 2023 02:24:08 +0100 Subject: [PATCH 17/36] Fix #8 hopefully --- README.md | 17 ++++++++++++----- source/server/developersession.d | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b1daafe..f048c80 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,20 @@ I am here to help! ```sh $ sideloader -h Available commands: + app-id list List App IDs. + app-id add Add a new App ID. + app-id delete Delete an App ID (it won't let you create more App IDs though). + app-id download Download the provisioning profile for an App ID + cert list List certificates. + cert submit Submit a certificate signing request to Apple servers. + cert revoke Revoke a certificate. install Install an application on the device (renames the app, register the identifier, sign and install automatically). - sign Sign an application bundle (you need to have the App ID registered - though). - - - + sign Sign an application bundle. + team list List teams. + tool list List tools. + tool run Run a tool. + version Print the version. ``` ## How to install diff --git a/source/server/developersession.d b/source/server/developersession.d index 62284a5..a1f8de9 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -275,8 +275,8 @@ class DeveloperSession { ); } ).array(), - dict["maxQuantity"].uinteger().native(), - dict["availableQuantity"].uinteger().native(), + "maxQuantity" in dict ? dict["maxQuantity"].uinteger().native() : ulong.max, + "availableQuantity" in dict ? dict["availableQuantity"].uinteger().native() : ulong.max, )), (DeveloperPortalError err) => DeveloperPortalResponse(err) ); From fc8589010bbc4700efcea245a2ff2db0769e263b Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sun, 17 Dec 2023 02:41:19 +0100 Subject: [PATCH 18/36] Another commit to fix #8 --- source/server/developersession.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/developersession.d b/source/server/developersession.d index a1f8de9..7853d2c 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -271,7 +271,7 @@ class DeveloperSession { appId["identifier"].str().native(), appId["name"].str().native(), appId["features"].dict(), - appId["expirationDate"].date().native(), + "expirationDate" in appId ? appId["expirationDate"].date().native() : DateTime.max(), ); } ).array(), From 687fd5b12520ab8094849d5c77837b0e89cb01e1 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 20 Dec 2023 00:39:04 +0100 Subject: [PATCH 19/36] Fix silly error in the CLI frontend --- frontends/cli/source/cli_frontend.d | 2 +- source/utils.d | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/cli/source/cli_frontend.d b/frontends/cli/source/cli_frontend.d index 6533fb9..2597ab9 100644 --- a/frontends/cli/source/cli_frontend.d +++ b/frontends/cli/source/cli_frontend.d @@ -130,7 +130,7 @@ DeveloperSession login(Device device, ADI adi, bool interactive) { sendCode(); continue; } - } while (submitCode(code).match!((Success _) => true, (ReloginNeeded _) => true, (AppleLoginError _) => false)); + } while (submitCode(code).match!((Success _) => false, (ReloginNeeded _) => false, (AppleLoginError _) => true)); }) .match!( (DeveloperSession session) => session, diff --git a/source/utils.d b/source/utils.d index 2f0038c..185c6d8 100644 --- a/source/utils.d +++ b/source/utils.d @@ -48,6 +48,7 @@ private struct Delegate(alias U) } alias expand = internalExpand.expand; alias expand this; + // alias opCall = internalExpand.expand; } pragma(inline, true) From ed8a06312213c6ea1ae5b6f8e3744f6df8b0a1f3 Mon Sep 17 00:00:00 2001 From: Dadoum <24679280+Dadoum@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:35:16 +0000 Subject: [PATCH 20/36] OPENSSL EOKIZPOR FZJ ERIK,;FZPEO$SD I should really remove the openssl dependency. --- dub.selections.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dub.selections.json b/dub.selections.json index 63b10ac..a93d610 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -30,7 +30,7 @@ "plist": "~master", "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.1", + "requests": "2.1.2", "silly": "1.2.0-dev.2", "slf4d": "2.4.3", "test_allocator": "0.3.4", From 61edf5d16790e4d43a8b55f53bf08f09c99ec959 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sun, 24 Dec 2023 13:04:56 +0100 Subject: [PATCH 21/36] Merge dub selections for all packages --- dub.selections.json | 5 +++- frontends/cli/dub.selections.json | 27 +-------------------- frontends/dlangui/dub.selections.json | 35 +-------------------------- frontends/gtk/dub.selections.json | 23 +----------------- frontends/swiftui/dub.selections.json | 22 +---------------- 5 files changed, 8 insertions(+), 104 deletions(-) mode change 100644 => 120000 frontends/cli/dub.selections.json mode change 100644 => 120000 frontends/dlangui/dub.selections.json mode change 100644 => 120000 frontends/gtk/dub.selections.json mode change 100644 => 120000 frontends/swiftui/dub.selections.json diff --git a/dub.selections.json b/dub.selections.json index a93d610..4888366 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -24,13 +24,16 @@ "inilike": "1.2.2", "intel-intrinsics": "1.11.15", "isfreedesktop": "0.1.1", - "keyring": {"path":"keyring/"}, + "jcli": "0.24.0", + "keyring": {"path":"../../keyring/"}, "memutils": "1.0.9", "mir-core": "1.6.0", "plist": "~master", "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "progress": "5.0.2", "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, "requests": "2.1.2", + "sideloader": {"path":"../../"}, "silly": "1.2.0-dev.2", "slf4d": "2.4.3", "test_allocator": "0.3.4", diff --git a/frontends/cli/dub.selections.json b/frontends/cli/dub.selections.json deleted file mode 100644 index 33047f0..0000000 --- a/frontends/cli/dub.selections.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "fileVersion": 1, - "versions": { - "automem": "0.6.9", - "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, - "botan-math": "1.0.4", - "cachetools": "0.4.1", - "dxml": "0.4.4", - "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, - "gtk_d": "1.0.3", - "intel-intrinsics": "1.11.15", - "jcli": "0.24.0", - "keyring": {"path":"../../keyring"}, - "memutils": "1.0.9", - "plist": "~master", - "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, - "progress": "5.0.2", - "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.1", - "sideloader": {"path":"../../"}, - "slf4d": "2.4.3", - "test_allocator": "0.3.4", - "unit-threaded": "0.10.8", - "windows-headers": "1.0.5" - } -} diff --git a/frontends/cli/dub.selections.json b/frontends/cli/dub.selections.json new file mode 120000 index 0000000..43e1d75 --- /dev/null +++ b/frontends/cli/dub.selections.json @@ -0,0 +1 @@ +../../dub.selections.json \ No newline at end of file diff --git a/frontends/dlangui/dub.selections.json b/frontends/dlangui/dub.selections.json deleted file mode 100644 index 89823fb..0000000 --- a/frontends/dlangui/dub.selections.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "fileVersion": 1, - "versions": { - "arsd-official": "10.9.10", - "automem": "0.6.9", - "bindbc-freetype": "1.0.5", - "bindbc-loader": "1.0.3", - "bindbc-opengl": "1.0.5", - "bindbc-sdl": "1.0.1", - "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, - "botan-math": "1.0.4", - "cachetools": "0.4.1", - "dlangui": "0.10.4", - "dsfml": "2.1.1", - "dxml": "0.4.4", - "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, - "glx-d": "1.1.0", - "icontheme": "1.2.3", - "inilike": "1.2.2", - "intel-intrinsics": "1.11.15", - "isfreedesktop": "0.1.1", - "memutils": "1.0.9", - "plist": "~master", - "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, - "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.1", - "sideloader": {"path":"../../"}, - "slf4d": "2.4.3", - "test_allocator": "0.3.4", - "unit-threaded": "0.10.8", - "x11": "1.0.21", - "xdgpaths": "0.2.5" - } -} diff --git a/frontends/dlangui/dub.selections.json b/frontends/dlangui/dub.selections.json new file mode 120000 index 0000000..43e1d75 --- /dev/null +++ b/frontends/dlangui/dub.selections.json @@ -0,0 +1 @@ +../../dub.selections.json \ No newline at end of file diff --git a/frontends/gtk/dub.selections.json b/frontends/gtk/dub.selections.json deleted file mode 100644 index 8132870..0000000 --- a/frontends/gtk/dub.selections.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "fileVersion": 1, - "versions": { - "automem": "0.6.9", - "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, - "botan-math": "1.0.4", - "cachetools": "0.4.1", - "dxml": "0.4.4", - "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, - "gtk_d": "1.0.3", - "intel-intrinsics": "1.11.15", - "memutils": "1.0.9", - "plist": "~master", - "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, - "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.1", - "sideloader": {"path":"../../"}, - "slf4d": "2.4.3", - "test_allocator": "0.3.4", - "unit-threaded": "0.10.8" - } -} diff --git a/frontends/gtk/dub.selections.json b/frontends/gtk/dub.selections.json new file mode 120000 index 0000000..43e1d75 --- /dev/null +++ b/frontends/gtk/dub.selections.json @@ -0,0 +1 @@ +../../dub.selections.json \ No newline at end of file diff --git a/frontends/swiftui/dub.selections.json b/frontends/swiftui/dub.selections.json deleted file mode 100644 index 2eea410..0000000 --- a/frontends/swiftui/dub.selections.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "fileVersion": 1, - "versions": { - "automem": "0.6.9", - "botan": {"version":"a0c206639debc7e5726c02fc399267e6a33571a0","repository":"git+https://github.com/Dadoum/botan.git"}, - "botan-math": "1.0.4", - "cachetools": "0.4.1", - "dxml": "0.4.4", - "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, - "intel-intrinsics": "1.11.15", - "memutils": "1.0.9", - "plist": "~master", - "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, - "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.1", - "sideloader": {"path":"../../"}, - "slf4d": "2.4.3", - "test_allocator": "0.3.4", - "unit-threaded": "0.10.8" - } -} diff --git a/frontends/swiftui/dub.selections.json b/frontends/swiftui/dub.selections.json new file mode 120000 index 0000000..43e1d75 --- /dev/null +++ b/frontends/swiftui/dub.selections.json @@ -0,0 +1 @@ +../../dub.selections.json \ No newline at end of file From bb454e2b9616aec7dbf076288bb62d5efcd004a5 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Mon, 25 Dec 2023 17:40:05 +0100 Subject: [PATCH 22/36] Overhaul the CLI frontend --- dub.selections.json | 2 +- frontends/cli/dub.json | 2 +- frontends/cli/source/app_id.d | 97 +++++++++++++---------------- frontends/cli/source/certificate.d | 81 +++++++++++------------- frontends/cli/source/cli_frontend.d | 85 ++++++++++++++++++------- frontends/cli/source/install.d | 18 ++---- frontends/cli/source/sign.d | 33 ++++------ frontends/cli/source/team.d | 28 ++++++--- frontends/cli/source/tool.d | 52 ++++++++++------ 9 files changed, 211 insertions(+), 187 deletions(-) diff --git a/dub.selections.json b/dub.selections.json index 4888366..2ef5257 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,6 +1,7 @@ { "fileVersion": 1, "versions": { + "argparse": "1.3.0", "arsd-official": "10.9.10", "automem": "0.6.9", "bindbc-freetype": "1.0.5", @@ -24,7 +25,6 @@ "inilike": "1.2.2", "intel-intrinsics": "1.11.15", "isfreedesktop": "0.1.1", - "jcli": "0.24.0", "keyring": {"path":"../../keyring/"}, "memutils": "1.0.9", "mir-core": "1.6.0", diff --git a/frontends/cli/dub.json b/frontends/cli/dub.json index 0e14dca..a98383a 100644 --- a/frontends/cli/dub.json +++ b/frontends/cli/dub.json @@ -10,7 +10,7 @@ "dependencies": { "sideloader": { "path": "../../" }, - "jcli": "~>0.24.0", + "argparse": "~>1.3.0", "keyring": { "path": "../../keyring" }, "progress": "~>5.0.2", "slf4d": "~>2" diff --git a/frontends/cli/source/app_id.d b/frontends/cli/source/app_id.d index 61c41e7..42a5ef1 100644 --- a/frontends/cli/source/app_id.d +++ b/frontends/cli/source/app_id.d @@ -5,36 +5,45 @@ import std.array; import std.exception; import file = std.file; import std.stdio; +import std.sumtype; import std.typecons; import slf4d; import slf4d.default_provider; -import jcli; +import argparse; import server.developersession; import cli_frontend; -// @Command("app-id", "Manage App IDs.") +@(Command("app-id").Description("Manage App IDs.")) +struct AppIdCommand +{ + int opCall() + { + return cmd.match!( + (ListAppIds cmd) => cmd(), + (AddAppId cmd) => cmd(), + (DeleteAppId cmd) => cmd(), + (DownloadProvision cmd) => cmd() + ); + } + + @SubCommands + SumType!(ListAppIds, AddAppId, DeleteAppId, DownloadProvision) cmd; +} -@Command("app-id list", "List App IDs.") +@(Command("list").Description("List App IDs.")) struct ListAppIds { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -51,7 +60,7 @@ struct ListAppIds auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } @@ -71,29 +80,22 @@ struct ListAppIds } } -@Command("app-id add", "Add a new App ID.") +@(Command("add").Description("Add a new App ID.")) struct AddAppId { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - @ArgPositional("app name") + @(PositionalArgument(0).Description("app name")) string name; - @ArgPositional("app identifier") + @(PositionalArgument(0).Description("app identifier")) string identifier; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -110,7 +112,7 @@ struct AddAppId auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } @@ -126,26 +128,19 @@ struct AddAppId } } -@Command("app-id delete", "Delete an App ID (it won't let you create more App IDs though).") +@(Command("delete").Description("Delete an App ID (it won't let you create more App IDs though).")) struct DeleteAppId { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - @ArgPositional("app identifier") + @(PositionalArgument(0).Description("app identifier")) string identifier; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -162,7 +157,6 @@ struct DeleteAppId auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } @@ -187,29 +181,22 @@ struct DeleteAppId } } -@Command("app-id download", "Download the provisioning profile for an App ID") +@(Command("download").Description("Download the provisioning profile for an App ID")) struct DownloadProvision { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - @ArgNamed("output|o", "Output file") + @(NamedArgument("o", "output").Description("Output file").Required()) string outputPath; - @ArgPositional("app identifier") + @(PositionalArgument(0).Description("app identifier")) string identifier; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -226,7 +213,7 @@ struct DownloadProvision auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } diff --git a/frontends/cli/source/certificate.d b/frontends/cli/source/certificate.d index 7dd4405..e450601 100644 --- a/frontends/cli/source/certificate.d +++ b/frontends/cli/source/certificate.d @@ -4,6 +4,7 @@ import std.algorithm; import std.array; import std.exception; import std.stdio; +import std.sumtype; import std.typecons; import slf4d; @@ -12,31 +13,38 @@ import slf4d.default_provider; import botan.cert.x509.pkcs10; import botan.filters.data_src; -import jcli; +import argparse; import server.developersession; import cli_frontend; -// @Command("cert", "Manage certificates.") +@(Command("cert").Description("Manage certificates.")) +struct CertificateCommand +{ + int opCall() + { + return cmd.match!( + (ListCerts cmd) => cmd(), + (SubmitCert cmd) => cmd(), + (RevokeCert cmd) => cmd() + ); + } + + @SubCommands + SumType!(ListCerts, SubmitCert, RevokeCert) cmd; +} -@Command("cert list", "List certificates.") +@(Command("list").Description("List certificates.")) struct ListCerts { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -53,7 +61,7 @@ struct ListCerts auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } @@ -73,31 +81,24 @@ struct ListCerts } } -// @Command("cert register", "Register a certificate for Sideloader if we don't already have one.") +// @(Command("register").Description("Register a certificate for Sideloader if we don't already have one.")) -@Command("cert submit", "Submit a certificate signing request to Apple servers.") +@(Command("submit").Description("Submit a certificate signing request to Apple servers.")) struct SubmitCert { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - @ArgPositional("CSR file") - @BindWith!readFile - ubyte[] certificateData; + @(PositionalArgument(0).Description("CSR file")) + string certificatePath; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - + ubyte[] certificateData = readFile(certificatePath); auto cert = PKCS10Request(DataSourceMemory(certificateData.ptr, certificateData.length)); - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -114,7 +115,7 @@ struct SubmitCert auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } @@ -128,27 +129,19 @@ struct SubmitCert } } - -@Command("cert revoke", "Revoke a certificate.") +@(Command("revoke").Description("Revoke a certificate.")) struct RevokeCert { mixin LoginCommand; - @ArgNamed("team", "Team ID") - Nullable!string teamId = null; + @(NamedArgument("team").Description("Team ID")) + string teamId = null; - @ArgPositional("certificate serial number") + @(PositionalArgument(0).Description("certificate serial number")) string serialNumber; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); @@ -165,7 +158,7 @@ struct RevokeCert auto teams = appleAccount.listTeams().unwrap(); - string teamId = this.teamId.get(null); + string teamId = this.teamId; if (teamId != null) { teams = teams.filter!((elem) => elem.teamId == teamId).array(); } diff --git a/frontends/cli/source/cli_frontend.d b/frontends/cli/source/cli_frontend.d index 2597ab9..5ee2779 100644 --- a/frontends/cli/source/cli_frontend.d +++ b/frontends/cli/source/cli_frontend.d @@ -11,6 +11,7 @@ import std.process; import std.stdio; import std.sumtype; import std.string; +import std.traits; import std.typecons; import file = std.file; @@ -37,44 +38,49 @@ import sideload.application; import sideload.certificateidentity; import sideload.sign; -import jcli; +import argparse; import app; import utils; version = X509; +noreturn wrongArgument(string msg) { + getLogger().error(msg); + exit(1); +} + auto openApp(string path) { if (!file.exists(path)) - return fail!Application("The specified app file does not exist."); + return wrongArgument("The specified app file does not exist."); if (!path.endsWith(".ipa")) - return fail!Application("The app is not an ipa file."); + return wrongArgument("The app is not an ipa file."); if (!file.isFile(path)) - return fail!Application("The app should be an ipa file."); + return wrongArgument("The app should be an ipa file."); - return ok!Application(new Application(path)); + return new Application(path); } auto openAppFolder(string path) { if (!file.exists(path)) - return fail!Application("The specified app file does not exist."); + return wrongArgument("The specified app file does not exist."); if (file.isFile(path)) - return fail!Application("The app should be a folder."); + return wrongArgument("The app should be a folder."); - return ok!Application(new Application(path)); + return new Application(path); } auto readFile(string path) { - return ok!(ubyte[])(cast(ubyte[]) file.read(path)); + return cast(ubyte[]) file.read(path); } auto readPrivateKey(string path) { RandomNumberGenerator rng = RandomNumberGenerator.makeRng(); - return ok!RSAPrivateKey(RSAPrivateKey(loadKey(path, rng))); + return RSAPrivateKey(loadKey(path, rng)); } auto readCertificate(string path) { @@ -141,8 +147,6 @@ DeveloperSession login(Device device, ADI adi, bool interactive) { ); } -// alias BindWith(alias U) = UseConverter!U; - auto initializeADI(string configurationPath) { scope log = getLogger(); @@ -189,35 +193,68 @@ string defaultConfigurationPath() import app_id; import certificate; import install; -// @Command("login", "Log-in to your Apple account.") -// @Command("logout", "Log-out.") +// @(Command("login").Description("Log-in to your Apple account.")) +// @(Command("logout").Description("Log-out.")) import sign; -// @Command("swift-setup", "Set-up certificates to build a Swift Package Manager iOS application (requires SPM in the path).") +// @(Command("swift-setup").Description("Set-up certificates to build a Swift Package Manager iOS application (requires SPM in the path).")) import team; import tool; -// @Command("tweak", "Install a tweak in an ipa file.") +// @(Command("tweak").Description("Install a tweak in an ipa file.")) mixin template LoginCommand() { import provision; - @ArgNamed("i", "Prompt to type passwords if needed.") + @(NamedArgument("i", "interactive").Description("Prompt to type passwords if needed.")) bool interactive = false; final auto login(Device device, ADI adi) => cli_frontend.login(device, adi, interactive); } -@Command("version", "Print the version.") +@(Command("version").Description("Print the version.")) struct VersionCommand { - void onExecute() { + int opCall() { writeln(versionStr); + return 0; + } +} + +int entryPoint(Commands commands) +{ + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + configureLoggingProvider(new shared DefaultProvider(true, commands.debug_ ? Levels.DEBUG : Levels.INFO)); + + try + { + return commands.cmd.match!( + (AppIdCommand cmd) => cmd(), + (CertificateCommand cmd) => cmd(), + (InstallCommand cmd) => cmd(), + (SignCommand cmd) => cmd(), + (TeamCommand cmd) => cmd(), + (ToolCommand cmd) => cmd(), + (VersionCommand cmd) => cmd(), + ); + } + catch (Exception ex) + { + getLogger().errorF!"%s at %s:%d: %s"(typeid(ex).name, ex.file, ex.line, ex.msg); + getLogger().debugF!"Full exception: %s"(ex); + return 1; } } -int main(string[] args) +struct Commands { - import keyring; - auto kr = makeKeyring(); + @(NamedArgument("d", "debug").Description("Enable debug logging")) + bool debug_; - return new CommandLineInterface!(app_id, certificate, install, sign, team, tool, cli_frontend)().parseAndExecute(args); - // return matchAndExecuteAcrossModules!(app_id, certificate, install, sign, team, tool, cli_frontend)(args); + @SubCommands + SumType!(AppIdCommand, CertificateCommand, InstallCommand, SignCommand, TeamCommand, ToolCommand, VersionCommand) cmd; } + +mixin CLI!Commands.main!entryPoint; + diff --git a/frontends/cli/source/install.d b/frontends/cli/source/install.d index f30e0e5..c8df8c9 100644 --- a/frontends/cli/source/install.d +++ b/frontends/cli/source/install.d @@ -3,7 +3,7 @@ module install; import slf4d; import slf4d.default_provider; -import jcli; +import argparse; import progress; import imobiledevice; @@ -13,23 +13,17 @@ import sideload.application; import cli_frontend; -@Command("install", "Install an application on the device (renames the app, register the identifier, sign and install automatically).") +@(Command("install").Description("Install an application on the device (renames the app, register the identifier, sign and install automatically).")) struct InstallCommand { mixin LoginCommand; - @ArgPositional("app path", "The path of the IPA file to sideload.") - @BindWith!openApp - Application app; + @(PositionalArgument(0, "app path").Description("The path of the IPA file to sideload.")) + string appPath; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); + Application app = openApp(appPath); auto log = getLogger(); diff --git a/frontends/cli/source/sign.d b/frontends/cli/source/sign.d index 1d7a269..eea455c 100644 --- a/frontends/cli/source/sign.d +++ b/frontends/cli/source/sign.d @@ -5,7 +5,7 @@ import slf4d.default_provider; import botan.pubkey.algo.rsa; -import jcli; +import argparse; import progress; import server.developersession; @@ -16,35 +16,28 @@ import sideload.sign: sideloadSign = sign; import cli_frontend; -@Command("sign", "Sign an application bundle.") +@(Command("sign").Description("Sign an application bundle.")) struct SignCommand { - @ArgNamed("cert|c", "Certificate (signed by Apple).") + @(NamedArgument("c", "cert").Description("Certificate (signed by Apple).").Required()) string certificatePath; - @ArgNamed("key|k", "Private key (matching the signed certificate).") - @BindWith!readPrivateKey - RSAPrivateKey privateKey; + @(NamedArgument("k", "key").Description("Private key (matching the signed certificate).").Required()) + string privateKeyPath; - @ArgNamed("provision|m", "App's provisioning certificate.") - @BindWith!readFile - ubyte[] mobileProvisionFile; + @(NamedArgument("m", "provision").Description("App's provisioning certificate.").Required()) + string mobileProvisionPath; - @ArgPositional("app path", "App path.") - @BindWith!openAppFolder - Application app; + @(PositionalArgument(0, "app path").Description("App path.")) + string appFolder; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); + RSAPrivateKey privateKey = readPrivateKey(privateKeyPath); + ubyte[] mobileProvisionFile = readFile(mobileProvisionPath); + Application app = openAppFolder(appFolder); scope certificate = readCertificate(certificatePath); string configurationPath = systemConfigurationPath(); diff --git a/frontends/cli/source/team.d b/frontends/cli/source/team.d index c2fedd9..88146a9 100644 --- a/frontends/cli/source/team.d +++ b/frontends/cli/source/team.d @@ -4,31 +4,39 @@ import std.algorithm; import std.array; import std.exception; import std.stdio; +import std.sumtype; import std.typecons; import slf4d; import slf4d.default_provider; -import jcli; +import argparse; import server.developersession; import cli_frontend; -@Command("team list", "List teams.") +@(Command("team").Description("Manage teams.")) +struct TeamCommand +{ + int opCall() + { + return cmd.match!( + (ListTeams cmd) => cmd() + ); + } + + @SubCommands + SumType!(ListTeams) cmd; +} + +@(Command("list").Description("List teams.")) struct ListTeams { mixin LoginCommand; - int onExecute() + int opCall() { - version (linux) { - import core.stdc.locale; - setlocale(LC_ALL, ""); - } - - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - auto log = getLogger(); string configurationPath = systemConfigurationPath(); diff --git a/frontends/cli/source/tool.d b/frontends/cli/source/tool.d index 9f563f9..f01bd11 100644 --- a/frontends/cli/source/tool.d +++ b/frontends/cli/source/tool.d @@ -5,12 +5,13 @@ import std.array; import std.exception; import std.format; import std.stdio; +import std.sumtype; import std.typecons; import slf4d; import slf4d.default_provider; -import jcli; +import argparse; import imobiledevice; @@ -18,19 +19,32 @@ import tools; import cli_frontend; -@Command("tool list", "List tools.") +@(Command("tool").Description("Run Sideloader's tools.")) +struct ToolCommand +{ + int opCall() + { + return cmd.match!( + (ListTools cmd) => cmd(), + (RunTool cmd) => cmd(), + ); + } + + @SubCommands + SumType!(ListTools, RunTool) cmd; +} + +@(Command("list").Description("List tools.")) struct ListTools { - @ArgNamed("udid", "iDevice UDID") - Nullable!string udid = null; + @(NamedArgument("udid").Description("iDevice UDID")) + string udid = null; - int onExecute() + int opCall() { - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - string deviceId; - if (auto udid = udid.get()) { + if (auto udid = udid) { deviceId = udid; } else { auto deviceList = iDevice.deviceList(); @@ -62,22 +76,20 @@ struct ListTools } } -@Command("tool run", "Run a tool.") +@(Command("run").Description("Run a tool.")) struct RunTool { - @ArgPositional("tool index", "The index of the tool to run (use `tool list` to see these indexes).") - int toolIndex; + @(PositionalArgument(0, "tool index").Description("The index of the tool to run (use `tool list` to see these indexes).")) + size_t toolIndex; - @ArgNamed("udid", "iDevice UDID.") - Nullable!string udid = null; + @(NamedArgument("udid").Description("iDevice UDID.")) + string udid = null; - int onExecute() + int opCall() { - configureLoggingProvider(new shared DefaultProvider(true, Levels.INFO)); - string deviceId; - if (auto udid = udid.get()) { + if (udid) { deviceId = udid; } else { auto deviceList = iDevice.deviceList(); @@ -94,17 +106,17 @@ struct RunTool iDevice device = new iDevice(deviceId); - auto tool = toolList(device)[toolIndex]; + auto tool = toolList(device)[cast(size_t) toolIndex]; if (tool.diagnostic != null) { getLogger().errorF!"The tool cannot be run: %s"(tool.diagnostic); return 1; } tool.run((message, canCancel) { - message = format!"%s [press return to continue]%s"(message, canCancel ? " [press ^C to quit]" : ""); + message = format!"%s [OK = return]%s"(message, canCancel ? " [exit = ^C]" : ""); stdout.writeln(message); readln(); - return true; + return false; }); return 0; From 45cecdab4c108e0f382a84760d69f08da7cf4a24 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Wed, 27 Dec 2023 23:52:24 +0100 Subject: [PATCH 23/36] Add scaffolding for a Qt frontend --- .github/workflows/build-qt.yml | 294 +++++++++++++++++++++++ README.md | 102 ++++---- dub.json | 4 +- frontends/dlangui/source/main.d | 24 +- frontends/dlangui/source/ui/loginframe.d | 3 +- frontends/dlangui/source/ui/mainframe.d | 22 +- frontends/qt/dub.json | 51 ++++ frontends/qt/dub.selections.json | 46 ++++ frontends/qt/resources/mainwindow.ui | 74 ++++++ frontends/qt/source/main.d | 63 +++++ frontends/qt/source/ui/mainwindow.d | 27 +++ 11 files changed, 628 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/build-qt.yml create mode 100644 frontends/qt/dub.json create mode 100644 frontends/qt/dub.selections.json create mode 100644 frontends/qt/resources/mainwindow.ui create mode 100644 frontends/qt/source/main.d create mode 100644 frontends/qt/source/ui/mainwindow.d diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml new file mode 100644 index 0000000..7745c09 --- /dev/null +++ b/.github/workflows/build-qt.yml @@ -0,0 +1,294 @@ +name: Qt builds + +on: push + +env: + BUILD_TYPE: Release + +jobs: + build-linux-x86_64: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils qtbase5-dev + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 :qt-frontend + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-x86_64" + + - name: Rename + run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-qt-linux-x86_64" + + - name: Put debug symbols in a separate file + run: eu-strip --strip-debug -f "${{github.workspace}}/bin/sideloader-qt-linux-x86_64.dbg" "${{github.workspace}}/bin/sideloader-qt-linux-x86_64" + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-qt-linux-x86_64 + path: | + ${{github.workspace}}/bin/sideloader-qt-linux-x86_64 + ${{github.workspace}}/bin/sideloader-qt-linux-x86_64.dbg + + build-macos-x86_64: + # if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Set up Homebrew + id: set-up-homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: '==3.1.*' + version: '5.15.2' + host: 'mac' + target: 'desktop' + arch: 'clang_64' + archives: 'qtbase' + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "x86_64-apple-darwin": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld-15", + "-Xcc=-target", + "-Xcc=x86_64-apple-darwin", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-Xcc=-F", + "-Xcc=$HOME/ldc2-1.33.0-osx-x86_64/lib", + "-Xcc=-mmacosx-version-min=12.6", + "-L=-platform_version", + "-L=macos", + "-L=12.6.0", + "-L=0.0.0", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-x86_64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-x86_64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-x86_64.tar.xz + tar -xf ./ldc2-1.33.0-osx-x86_64.tar.xz -C $HOME + brew fetch --force --bottle-tag=monterey llvm@16 + brew install $(brew --cache --bottle-tag=monterey llvm@16) + cp /home/linuxbrew/.linuxbrew/Cellar/llvm@16/16.0.6/lib/c++/* $HOME/ldc2-1.33.0-osx-x86_64/lib + rm -rf /home/linuxbrew/.linuxbrew + + - name: Set-up Qt for cross-compilation + run: + cp -r $Qt5_DIR/lib/* $HOME/ldc2-1.33.0-osx-x86_64/lib + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld-15 + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 --arch x86_64-apple-darwin :qt-frontend + + # - name: Rename + # run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-macOS-x86_64" + + - name: Build bundle + run: | + cp bin/sideloader ${{github.workspace}}/macos/resources/Sideloader.app/Contents/MacOS + cp -r $Qt5_DIR/plugins ${{github.workspace}}/macos/resources/Sideloader.app/Contents/ + cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks + rm -rf ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ + tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/macos/resources/ Sideloader.app + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-macOS-x86_64 + path: | + ${{github.workspace}}/Sideloader-qt.app.tgz + + build-macos-arm64: + # if: false + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Set up Homebrew + id: set-up-homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: '==3.1.*' + version: '5.15.2' + host: 'mac' + target: 'desktop' + arch: 'clang_64' + archives: 'qtbase' + + - name: Set-up macOS cross-compilation + run: | + mkdir -p $HOME/.ldc/ + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz + tar -xf ./MacOSX11.0.sdk.tar.xz -C $HOME + cat << EOF | tee $HOME/.ldc/ldc2.conf + "arm64-apple-macos": + { + // default switches injected before all explicit command-line switches + switches = [ + "-gcc=clang", + "-linker=lld-15", + "-Xcc=-target", + "-Xcc=arm64-apple-macos", + "-Xcc=-isysroot", + "-Xcc=$HOME/MacOSX11.0.sdk", + "-Xcc=-F", + "-Xcc=$HOME/ldc2-1.33.0-osx-arm64/lib", + "-Xcc=-mmacosx-version-min=12.6", + "-L=-platform_version", + "-L=macos", + "-L=12.6.0", + "-L=0.0.0", + "-defaultlib=phobos2-ldc,druntime-ldc", + ]; + // default switches appended after all explicit command-line switches + post-switches = [ + "-I$HOME/ldc2-1.33.0-osx-arm64/import", + ]; + // default directories to be searched for libraries when linking + lib-dirs = [ + "$HOME/ldc2-1.33.0-osx-arm64/lib", + ]; + }; + EOF + mkdir $HOME/ldc-macos + curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-osx-arm64.tar.xz + tar -xf ./ldc2-1.33.0-osx-arm64.tar.xz -C $HOME + brew fetch --force --bottle-tag=arm64_monterey llvm@16 + brew install $(brew --cache --bottle-tag=arm64_monterey llvm@16) + cp /home/linuxbrew/.linuxbrew/Cellar/llvm@16/16.0.6/lib/c++/* $HOME/ldc2-1.33.0-osx-arm64/lib + rm -rf /home/linuxbrew/.linuxbrew + + - name: Set-up Qt for cross-compilation + run: + cp -r $Qt5_DIR/lib/* $HOME/ldc2-1.33.0-osx-arm64/lib + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld-15 + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: dub build -b release-debug --compiler=ldc2 --arch arm64-apple-macos :qt-frontend + + # - name: Rename + # run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-macOS-arm64" + + - name: Build bundle + run: | + cp bin/sideloader ${{github.workspace}}/macos/resources/Sideloader.app/Contents/MacOS + cp -r $Qt5_DIR/plugins ${{github.workspace}}/macos/resources/Sideloader.app/Contents/ + cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks + rm -rf ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ + tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/macos/resources/ Sideloader.app + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-macOS-arm64 + path: | + ${{github.workspace}}/Sideloader-qt.app.tgz + + build-windows-x86_64: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: TheMrMilchmann/setup-msvc-dev@v3 + with: + toolset: 14.0 + arch: x64 + + - uses: dlang-community/setup-dlang@v1 + with: + compiler: ldc-1.33.0 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: '==3.1.*' + version: '5.15.2' + host: 'windows' + target: 'desktop' + dir: '${{github.workspace}}/qt/' + arch: 'win64_msvc2015_64' + archives: 'qtbase' + + - name: Write version file + run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d + + - name: Build + run: | + $env:LIB += ';' + $env:Qt5_Dir + '\lib' + echo $env:LIB + dub build -b release-debug --compiler=ldc2 :qt-frontend + + - name: Rename + run: | + mkdir "${{github.workspace}}/bin/platforms" + Copy-Item ($env:Qt5_DIR + '/plugins/platforms/qwindows.dll') "${{github.workspace}}/bin/platforms" + Copy-Item ($env:Qt5_DIR + '/bin/Qt5Core.dll'),($env:Qt5_DIR + '/bin/Qt5Gui.dll'),($env:Qt5_DIR + '/bin/Qt5Widgets.dll') "${{github.workspace}}/bin" + Compress-Archive -Path bin/* -DestinationPath sideloader-qt-windows.zip + + - uses: actions/upload-artifact@v3 + with: + name: sideloader-windows-x86_64 + path: | + ${{github.workspace}}/sideloader-qt-windows.zip diff --git a/README.md b/README.md index f048c80..de9153c 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,22 @@ Sideloader is an application made to install third-party applications on iOS dev You can see it as an open-source replacement of _Cydia Impactor_. -Currently only working on Linux (it was the priority since no easy alternatives exists). \ -Windows support is planned (it should not depend on iCloud!). \ -The CLI works too on all systems. +
Leave a star and a small tip if you feel like it! — more information at the end!
-I tried to make the code as readable as possible, if you struggle to understand anything -I am here to help! +## Current state -
Leave a star and a small tip if you feel like it! — more information at the end!
+Currently, there is a cross-platform CLI, with most features working. + +And there is a Linux frontend based on GTK 4. It was the priority since no real alternative existed +before. + +A Qt frontend is being made for Linux, Windows and macOS. + +A SwiftUI macOS GUI could be made (I got no Mac to work on that, but all the scaffolding code is here, +if someone wants to work on that). + +I tried to make the code as readable as possible, if you struggle to understand anything +I am here to help! I don't want this to finish unmaintained! ## Usage @@ -25,38 +33,44 @@ I am here to help! ```sh $ sideloader -h -Available commands: - app-id list List App IDs. - app-id add Add a new App ID. - app-id delete Delete an App ID (it won't let you create more App IDs though). - app-id download Download the provisioning profile for an App ID - cert list List certificates. - cert submit Submit a certificate signing request to Apple servers. - cert revoke Revoke a certificate. - install Install an application on the device (renames the app, register the - identifier, sign and install automatically). - sign Sign an application bundle. - team list List teams. - tool list List tools. - tool run Run a tool. - version Print the version. +Usage: sideloader [-d] [-h] [] + +Available commands: + app-id Manage App IDs. + cert Manage certificates. + install Install an application on the device (renames the app, register + the identifier, sign and install automatically). + sign Sign an application bundle. + team Manage teams. + tool Run Sideloader's tools. + version Print the version. + +Optional arguments: + -d, --debug Enable debug logging + -h, --help Show this help message and exit ``` ## How to install Currently, the only builds available can be downloaded through GitHub Actions. -On Linux, when it will be ready, it will be as simple as installing the Flatpak on your -system, or wait for someone to package it for your distribution. +CLI builds include builds for every supported operating system, and GTK+ builds have a GUI +for Linux-based OSes. -On Windows, it will get a GitHub Release with a simple executable to run. +When it will get a first release, there will probably be an easier download (not requiring an +account) in the Release tab of this repo, and hopefully it will be packaged as a Flatpak (even if +I currently don't see how to make it). -Currently, Windows GUI is not working, and the CLI is pretty bare-bones. - -Dependencies (runtime): libimobiledevice, libplist-2.X (I attempted to support both 2.2 +**Dependencies (at runtime):** libimobiledevice, libplist-2.X (I attempted to support both 2.2 and 2.3). OpenSSL is currently also needed, but I plan to remove that dependency as soon as possible (only networking is requiring it). +*Note:* On Windows, MSVC builds of those libraries are needed as sideloader is built with MSVC. +It also implies you have to install Microsoft Visual C++ redistributable to run it, but you probably +already have those installed. Put them then in the same folder as Sideloader and you'll be able to +run it. (For libimobiledevice and libplist, take a look at libimobiledevice-win32, and for OpenSSL +see [this link](https://slproweb.com/products/Win32OpenSSL.html)) + ## How do I build it myself? Get a recent version `ldc2` or `dmd` installed (an installation script is available on @@ -91,39 +105,11 @@ to add the entitlement for debugging)_ ## Features - Sideload +- Sign IPAs +- Set-up SideStore's pairing file +- Manage App IDs and certificates for free developer accounts. - iOS version range is unknown. 32-bit support is untested. Please report any issue here!! -A dedicated SideStore installer feature is planned with automated pairing file management. - -## Notes on platform support - -### Linux - -Linux version currently only features a GTK frontend. I made one because I was already -familiar with GTK+. I made it in plain code because I really dislike GTK Builder's XML. - -### Windows - -Windows version uses a Win32 frontend. On Windows 11, it looks terrible. The current -state of GUI libraries on Windows is rather unsatisfying: we have one old well-supported -library across the major versions of Windows, Win32, the one I am using, and then we have -a lot of unsuccessful attempts to supplant it, and finally we have WinUI 3, which looks -good but has no bindings whatsoever (WinRT is supported in C# and C++ only, I would have -to make the bindings myself, which I could do but would take some effort), and is not -supported everywhere (Windows 10+ only). This is why I used DFL, which is a Windows Forms -like wrapper of Win32 APIs. - -**Requirements:** a USB muxer, which is generally iTunes or anything -distributing AppleMobileDevice. netmuxd should work too if you can't install it for some -reason. OpenSSL is also required for networking, unfortunately. - -### macOS - -Only the CLI tool is available yet. - -GUI support is planned but low-priority as macOS already has more tools to install and -manage iOS apps. - ## Acknowledgements and references - [People on this thread](https://github.com/horrorho/InflatableDonkey/issues/87): first diff --git a/dub.json b/dub.json index df0ff40..edc75bf 100644 --- a/dub.json +++ b/dub.json @@ -36,6 +36,8 @@ "subPackages": [ "frontends/cli", "frontends/dlangui", - "frontends/gtk" + "frontends/gtk", + "frontends/qt", + "frontends/swiftui" ] } \ No newline at end of file diff --git a/frontends/dlangui/source/main.d b/frontends/dlangui/source/main.d index 8f1a10e..e2ecc6a 100644 --- a/frontends/dlangui/source/main.d +++ b/frontends/dlangui/source/main.d @@ -73,9 +73,30 @@ extern (C) int UIAppMain() { DependenciesFrame.ensureDeps(configurationPath, { Window w = Platform.instance.createWindow(applicationName, null, WindowFlag.ExpandSize | WindowFlag.Resizable, 350, 400); - w.mainWidget = new MainFrame(); + MainFrame frame = new MainFrame(); + w.mainWidget = frame; w.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; w.show(); + + auto log = getLogger(); + iDevice.subscribeEvent((ref const(iDeviceEvent) event) { + with (iDeviceEventType) switch (event.event) { + case iDeviceEventType.add: + log.infoF!"Device with UDID %s has been added."(event.udid); + break; + case iDeviceEventType.remove: + log.infoF!"Device with UDID %s has been removed."(event.udid); + break; + case iDeviceEventType.paired: + log.infoF!"Device with UDID %s has been paired."(event.udid); + break; + default: + log.infoF!"Device with UDID %s has been ???? (%s)."(event.udid, event.event); + break; + } + + frame.refreshDeviceList(); + }); }); return Platform.instance.enterMessageLoop(); @@ -90,4 +111,3 @@ private class SegmentationFault: Throwable /+ Throwable since it should not be c extern(C) void SIGSEGV_trace(int) @system { throw new SegmentationFault(); } - diff --git a/frontends/dlangui/source/ui/loginframe.d b/frontends/dlangui/source/ui/loginframe.d index 51998d3..fe43054 100644 --- a/frontends/dlangui/source/ui/loginframe.d +++ b/frontends/dlangui/source/ui/loginframe.d @@ -116,7 +116,8 @@ class LoginFrame: VerticalLayout { static void login(Device device, ADI adi, Window parentWindow, void delegate(DeveloperSession) onCompletion) { auto loginWindow = Platform.instance.createWindow("Log-in to Apple", parentWindow, WindowFlag.ExpandSize, 1, 1); - auto frame = new LoginFrame(device, adi, onCompletion);loginWindow.mainWidget = frame; + auto frame = new LoginFrame(device, adi, onCompletion); + loginWindow.mainWidget = frame; loginWindow.windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow; loginWindow.show(); frame.setBusy(false); diff --git a/frontends/dlangui/source/ui/mainframe.d b/frontends/dlangui/source/ui/mainframe.d index a167244..a30919c 100644 --- a/frontends/dlangui/source/ui/mainframe.d +++ b/frontends/dlangui/source/ui/mainframe.d @@ -287,25 +287,6 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ body.addChild(actionsFrame); } addChild(body); - - iDevice.subscribeEvent((ref const(iDeviceEvent) event) { - with (iDeviceEventType) switch (event.event) { - case iDeviceEventType.add: - log.infoF!"Device with UDID %s has been added."(event.udid); - break; - case iDeviceEventType.remove: - log.infoF!"Device with UDID %s has been removed."(event.udid); - break; - case iDeviceEventType.paired: - log.infoF!"Device with UDID %s has been paired."(event.udid); - break; - default: - log.infoF!"Device with UDID %s has been ???? (%s)."(event.udid, event.event); - break; - } - - refreshDeviceList(); - }); } void refreshDeviceList() { @@ -321,7 +302,8 @@ class MainFrame: VerticalLayout/+, MenuItemClickHandler, MenuItemActionHandler+/ } }); - window().executeInUiThread(() => window().invalidate()); + if (auto win = window()) + win.executeInUiThread(() => win.invalidate()); } void updateDeviceInfo(scope LockdowndClient client) { diff --git a/frontends/qt/dub.json b/frontends/qt/dub.json new file mode 100644 index 0000000..2cc1da3 --- /dev/null +++ b/frontends/qt/dub.json @@ -0,0 +1,51 @@ +{ + "name": "qt-frontend", + "targetType": "executable", + "targetPath": "../../bin/", + "targetName": "sideloader", + + "sourcePaths": [ + "source/" + ], + + "sourcePaths-linux": [ + "../common/linux/" + ], + + "sourcePaths-windows": [ + "../common/windows/" + ], + + "stringImportPaths": ["resources/"], + + "libs-windows": [ + "Qt5Core", + "Qt5Gui", + "Qt5Widgets" + ], + + "libs-linux": [ + "Qt5Core", + "Qt5Gui", + "Qt5Widgets" + ], + + "lflags-osx": [ + "-framework", + "QtCore", + "-framework", + "QtGui", + "-framework", + "QtWidgets", + "-rpath", + "@loader_path/../Frameworks" + ], + + "dependencies": { + "sideloader": { "path": "../../" }, + "dqt": { + "repository": "git+https://github.com/Dadoum/dqt.git", + "version": "9034b54601834d86306c1e0bce53a99f5971c84a" + } + } +} \ No newline at end of file diff --git a/frontends/qt/dub.selections.json b/frontends/qt/dub.selections.json new file mode 100644 index 0000000..2ef5257 --- /dev/null +++ b/frontends/qt/dub.selections.json @@ -0,0 +1,46 @@ +{ + "fileVersion": 1, + "versions": { + "argparse": "1.3.0", + "arsd-official": "10.9.10", + "automem": "0.6.9", + "bindbc-freetype": "1.0.5", + "bindbc-loader": "1.0.3", + "bindbc-opengl": "1.0.5", + "bindbc-sdl": "1.0.1", + "botan": "1.13.6", + "botan-math": "1.0.4", + "cachetools": "0.4.1", + "concepts": "0.0.9", + "concurrency": "5.0.4", + "dfl": {"version":"224d9348286620c8ea4854690a09e7380d6f5b2f","repository":"git+https://github.com/Dadoum/dfl.git"}, + "dlangui": "0.10.4", + "dsfml": "2.1.1", + "dxml": "0.4.4", + "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, + "glx-d": "1.1.0", + "gtk_d": "1.0.3", + "icontheme": "1.2.3", + "ikod-containers": "0.0.22", + "inilike": "1.2.2", + "intel-intrinsics": "1.11.15", + "isfreedesktop": "0.1.1", + "keyring": {"path":"../../keyring/"}, + "memutils": "1.0.9", + "mir-core": "1.6.0", + "plist": "~master", + "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "progress": "5.0.2", + "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, + "requests": "2.1.2", + "sideloader": {"path":"../../"}, + "silly": "1.2.0-dev.2", + "slf4d": "2.4.3", + "test_allocator": "0.3.4", + "undead": "1.1.8", + "unit-threaded": "0.10.8", + "windows-headers": "1.0.5", + "x11": "1.0.21", + "xdgpaths": "0.2.5" + } +} diff --git a/frontends/qt/resources/mainwindow.ui b/frontends/qt/resources/mainwindow.ui new file mode 100644 index 0000000..cb9b831 --- /dev/null +++ b/frontends/qt/resources/mainwindow.ui @@ -0,0 +1,74 @@ + + + MainWindow + + + + 0 + 0 + 358 + 457 + + + + Sideloader + + + + + + + + + + + Informations + + + + + Sideload + + + + + Additional tools + + + + + + + + + + 0 + 0 + 358 + 24 + + + + + Account + + + + + Devices + + + + + Help + + + + + + + + + + + diff --git a/frontends/qt/source/main.d b/frontends/qt/source/main.d new file mode 100644 index 0000000..609b493 --- /dev/null +++ b/frontends/qt/source/main.d @@ -0,0 +1,63 @@ +module main; + +import core.runtime; +import core.stdc.signal; + +import file = std.file; +import std.path; +import std.process; +import std.traits; + +import qt.core.coreapplication; +import qt.core.dir; +import qt.core.string; +import qt.core.stringlist; +import qt.widgets.application; + +import slf4d; +import slf4d.default_provider; +import slf4d.provider; + +version(Windows) { + import graphical_app; +} + +import constants; +import utils; + +import ui.mainwindow; + +int main(string[] args) { + version (linux) { + import core.stdc.locale; + setlocale(LC_ALL, ""); + } + + debug { + Level level = Levels.TRACE; + } else { + Level level = Levels.INFO; + } + + signal(SIGSEGV, cast(Parameters!signal[1]) &SIGSEGV_trace); + configureLoggingProvider(new shared DefaultProvider(true, level)); + + version (Windows) { + configureSegfaultHandler(); + } + + scope qtApp = new QApplication(Runtime.cArgs.argc, Runtime.cArgs.argv); + auto w = new MainWindow(); + w.show(); + return qtApp.exec(); +} + +private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { + this(string file = __FILE__, size_t line = __LINE__) { + super("Segmentation fault.", file, line); + } +} + +extern(C) void SIGSEGV_trace(int) @system { + throw new SegmentationFault(); +} diff --git a/frontends/qt/source/ui/mainwindow.d b/frontends/qt/source/ui/mainwindow.d new file mode 100644 index 0000000..305cd3d --- /dev/null +++ b/frontends/qt/source/ui/mainwindow.d @@ -0,0 +1,27 @@ +module ui.mainwindow; + +import core.stdcpp.new_: cpp_new; + +import qt.config; +import qt.core.coreevent; +import qt.core.string; +import qt.core.translator; +import qt.helpers; +import qt.widgets.action; +import qt.widgets.mainwindow; +import qt.widgets.ui; +import qt.widgets.widget; + +alias MainWindowUI = UIStruct!"mainwindow.ui"; + +class MainWindow: QMainWindow { + MainWindowUI* ui; + + mixin(Q_OBJECT_D); + this() { + setWindowTitle(QString("Sideloader")); + + ui = cpp_new!MainWindowUI(); + ui.setupUi(this); + } +} From 882d5d68a0e32d6a528e944796425d7a2c85fd1f Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 28 Dec 2023 00:05:30 +0100 Subject: [PATCH 24/36] Add macOS app folder structure --- .github/workflows/build-qt.yml | 23 ++++----- .../Sideloader.app/Contents/Frameworks/.keep | 0 .../macos/Sideloader.app/Contents/Info.plist | 28 +++++++++++ .../macos/Sideloader.app/Contents/MacOS/.keep | 0 .../Sideloader.app/Contents/Resources/.keep | 0 frontends/qt/dub.json | 4 +- frontends/qt/dub.selections.json | 47 +------------------ frontends/qt/source/main.d | 15 +++--- 8 files changed, 49 insertions(+), 68 deletions(-) create mode 100644 frontends/common/resources/macos/Sideloader.app/Contents/Frameworks/.keep create mode 100644 frontends/common/resources/macos/Sideloader.app/Contents/Info.plist create mode 100644 frontends/common/resources/macos/Sideloader.app/Contents/MacOS/.keep create mode 100644 frontends/common/resources/macos/Sideloader.app/Contents/Resources/.keep mode change 100644 => 120000 frontends/qt/dub.selections.json diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml index 7745c09..dfd94fd 100644 --- a/.github/workflows/build-qt.yml +++ b/.github/workflows/build-qt.yml @@ -27,9 +27,6 @@ jobs: - name: Build run: dub build -b release-debug --compiler=ldc2 :qt-frontend - - name: Rename - run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-x86_64" - - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-qt-linux-x86_64" @@ -131,11 +128,11 @@ jobs: - name: Build bundle run: | - cp bin/sideloader ${{github.workspace}}/macos/resources/Sideloader.app/Contents/MacOS - cp -r $Qt5_DIR/plugins ${{github.workspace}}/macos/resources/Sideloader.app/Contents/ - cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks - rm -rf ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ - tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/macos/resources/ Sideloader.app + cp bin/sideloader ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/MacOS + cp -r $Qt5_DIR/plugins ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/ + cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks + rm -rf ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ + tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/frontends/common/resources/macos/ Sideloader.app - uses: actions/upload-artifact@v3 with: @@ -231,11 +228,11 @@ jobs: - name: Build bundle run: | - cp bin/sideloader ${{github.workspace}}/macos/resources/Sideloader.app/Contents/MacOS - cp -r $Qt5_DIR/plugins ${{github.workspace}}/macos/resources/Sideloader.app/Contents/ - cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks - rm -rf ${{github.workspace}}/macos/resources/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ - tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/macos/resources/ Sideloader.app + cp bin/sideloader ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/MacOS + cp -r $Qt5_DIR/plugins ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/ + cp -r $Qt5_DIR/lib/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks + rm -rf ${{github.workspace}}/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks/Qt{Core,Gui,Widgets,DBus,PrintSupport}.framework/Versions/5/Headers/ + tar -czpvf Sideloader-qt.app.tgz -C ${{github.workspace}}/frontends/common/resources/macos/ Sideloader.app - uses: actions/upload-artifact@v3 with: diff --git a/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks/.keep b/frontends/common/resources/macos/Sideloader.app/Contents/Frameworks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/frontends/common/resources/macos/Sideloader.app/Contents/Info.plist b/frontends/common/resources/macos/Sideloader.app/Contents/Info.plist new file mode 100644 index 0000000..038bb77 --- /dev/null +++ b/frontends/common/resources/macos/Sideloader.app/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + sideloader + CFBundleGetInfoString + Sideloader + CFBundleIdentifier + dev.dadoum.Sideloader + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Sideloader + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.0.0 + CFBundleVersion + 0.0.0 + NSHumanReadableCopyright + 2023 Dadoum, license available on the official repo + NSPrincipalClass + NSApplication + + \ No newline at end of file diff --git a/frontends/common/resources/macos/Sideloader.app/Contents/MacOS/.keep b/frontends/common/resources/macos/Sideloader.app/Contents/MacOS/.keep new file mode 100644 index 0000000..e69de29 diff --git a/frontends/common/resources/macos/Sideloader.app/Contents/Resources/.keep b/frontends/common/resources/macos/Sideloader.app/Contents/Resources/.keep new file mode 100644 index 0000000..e69de29 diff --git a/frontends/qt/dub.json b/frontends/qt/dub.json index 2cc1da3..ce29e70 100644 --- a/frontends/qt/dub.json +++ b/frontends/qt/dub.json @@ -44,8 +44,8 @@ "dependencies": { "sideloader": { "path": "../../" }, "dqt": { - "repository": "git+https://github.com/Dadoum/dqt.git", - "version": "9034b54601834d86306c1e0bce53a99f5971c84a" + "repository": "git+https://github.com/tim-dlang/dqt.git", + "version": "101a998ee7f84e83543b04938ac6a67f673c9779" } } } \ No newline at end of file diff --git a/frontends/qt/dub.selections.json b/frontends/qt/dub.selections.json deleted file mode 100644 index 2ef5257..0000000 --- a/frontends/qt/dub.selections.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "fileVersion": 1, - "versions": { - "argparse": "1.3.0", - "arsd-official": "10.9.10", - "automem": "0.6.9", - "bindbc-freetype": "1.0.5", - "bindbc-loader": "1.0.3", - "bindbc-opengl": "1.0.5", - "bindbc-sdl": "1.0.1", - "botan": "1.13.6", - "botan-math": "1.0.4", - "cachetools": "0.4.1", - "concepts": "0.0.9", - "concurrency": "5.0.4", - "dfl": {"version":"224d9348286620c8ea4854690a09e7380d6f5b2f","repository":"git+https://github.com/Dadoum/dfl.git"}, - "dlangui": "0.10.4", - "dsfml": "2.1.1", - "dxml": "0.4.4", - "dynamic-loader": {"version":"65a8b8b8a6d44d47e63bddc985268592ecf47764","repository":"git+https://github.com/Dadoum/dynamicloader.git"}, - "glx-d": "1.1.0", - "gtk_d": "1.0.3", - "icontheme": "1.2.3", - "ikod-containers": "0.0.22", - "inilike": "1.2.2", - "intel-intrinsics": "1.11.15", - "isfreedesktop": "0.1.1", - "keyring": {"path":"../../keyring/"}, - "memutils": "1.0.9", - "mir-core": "1.6.0", - "plist": "~master", - "plist-d": {"version":"30d152e88767611e10048b25777ecb5f9075f87c","repository":"git+https://github.com/Dadoum/libplist-d.git"}, - "progress": "5.0.2", - "provision": {"version":"533dca306b86f9c7801354b78f5187addb58b740","repository":"git+https://github.com/Dadoum/Provision.git"}, - "requests": "2.1.2", - "sideloader": {"path":"../../"}, - "silly": "1.2.0-dev.2", - "slf4d": "2.4.3", - "test_allocator": "0.3.4", - "undead": "1.1.8", - "unit-threaded": "0.10.8", - "windows-headers": "1.0.5", - "x11": "1.0.21", - "xdgpaths": "0.2.5" - } -} diff --git a/frontends/qt/dub.selections.json b/frontends/qt/dub.selections.json new file mode 120000 index 0000000..43e1d75 --- /dev/null +++ b/frontends/qt/dub.selections.json @@ -0,0 +1 @@ +../../dub.selections.json \ No newline at end of file diff --git a/frontends/qt/source/main.d b/frontends/qt/source/main.d index 609b493..dd14f5b 100644 --- a/frontends/qt/source/main.d +++ b/frontends/qt/source/main.d @@ -18,10 +18,6 @@ import slf4d; import slf4d.default_provider; import slf4d.provider; -version(Windows) { - import graphical_app; -} - import constants; import utils; @@ -40,11 +36,16 @@ int main(string[] args) { } signal(SIGSEGV, cast(Parameters!signal[1]) &SIGSEGV_trace); - configureLoggingProvider(new shared DefaultProvider(true, level)); + version(Windows) { + import graphical_app; + SetUnhandledExceptionFilter(&SIGSEGV_win); - version (Windows) { - configureSegfaultHandler(); + import logging; + auto loggingProvider = new shared OutputDebugStringLoggingProvider(level); + } else { + auto loggingProvider = new shared DefaultProvider(true, level); } + configureLoggingProvider(loggingProvider); scope qtApp = new QApplication(Runtime.cArgs.argc, Runtime.cArgs.argv); auto w = new MainWindow(); From c6afa3fee3fdde6a54911d8795954a33d53d1e40 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 28 Dec 2023 00:14:26 +0100 Subject: [PATCH 25/36] Use my dqt fork --- frontends/qt/dub.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontends/qt/dub.json b/frontends/qt/dub.json index ce29e70..2cc1da3 100644 --- a/frontends/qt/dub.json +++ b/frontends/qt/dub.json @@ -44,8 +44,8 @@ "dependencies": { "sideloader": { "path": "../../" }, "dqt": { - "repository": "git+https://github.com/tim-dlang/dqt.git", - "version": "101a998ee7f84e83543b04938ac6a67f673c9779" + "repository": "git+https://github.com/Dadoum/dqt.git", + "version": "9034b54601834d86306c1e0bce53a99f5971c84a" } } } \ No newline at end of file From 2cdc10bb4c1ba122c4c67e47ae809392e712b442 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 28 Dec 2023 00:21:31 +0100 Subject: [PATCH 26/36] Fix Windows build --- frontends/common/windows/graphical_app.d | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontends/common/windows/graphical_app.d b/frontends/common/windows/graphical_app.d index 6efe4ae..427321a 100644 --- a/frontends/common/windows/graphical_app.d +++ b/frontends/common/windows/graphical_app.d @@ -4,10 +4,10 @@ import core.sys.windows.windef; public import core.sys.windows.winbase: SetUnhandledExceptionFilter; pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); -// static if (__VERSION__ >= 2091) -// pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); -// else -// pragma(linkerDirective, "/ENTRY:mainCRTStartup"); +static if (__VERSION__ >= 2091) + pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); +else + pragma(linkerDirective, "/ENTRY:mainCRTStartup"); private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { this(string file = __FILE__, size_t line = __LINE__) { From edc0f76129ec3f2400a1d915f9372fa5aef6b40e Mon Sep 17 00:00:00 2001 From: Dadoum Date: Thu, 28 Dec 2023 02:03:22 +0100 Subject: [PATCH 27/36] Implement basic Qt UI --- .github/workflows/build-qt.yml | 2 + frontends/qt/resources/mainwindow.ui | 217 ++++++++++++++++++++++++++- 2 files changed, 215 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml index dfd94fd..bc362d4 100644 --- a/.github/workflows/build-qt.yml +++ b/.github/workflows/build-qt.yml @@ -281,6 +281,8 @@ jobs: run: | mkdir "${{github.workspace}}/bin/platforms" Copy-Item ($env:Qt5_DIR + '/plugins/platforms/qwindows.dll') "${{github.workspace}}/bin/platforms" + mkdir "${{github.workspace}}/bin/styles" + Copy-Item ($env:Qt5_DIR + '/plugins/styles/qwindowsvistastyle.dll') "${{github.workspace}}/bin/styles" Copy-Item ($env:Qt5_DIR + '/bin/Qt5Core.dll'),($env:Qt5_DIR + '/bin/Qt5Gui.dll'),($env:Qt5_DIR + '/bin/Qt5Widgets.dll') "${{github.workspace}}/bin" Compress-Archive -Path bin/* -DestinationPath sideloader-qt-windows.zip diff --git a/frontends/qt/resources/mainwindow.ui b/frontends/qt/resources/mainwindow.ui index cb9b831..20cec31 100644 --- a/frontends/qt/resources/mainwindow.ui +++ b/frontends/qt/resources/mainwindow.ui @@ -6,34 +6,202 @@ 0 0 - 358 - 457 + 340 + 430 + + + 340 + 430 + + Sideloader + + 8 + + + 8 + + + 8 + + + 8 + + + false + + + 0 + + + false + + + false + Informations + + + + + true + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Device model: + + + + + + + Device name: + + + + + + + iOS version: + + + + + + + true + + + + Sideload + + + + + + + + + + ... + + + + + + + + + <span style=" color:#e01b24;"></span> + + + + + + + + + Bundle name: + + + + + + + Bundle identifier: + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + true + + + + + + + + + + Install + + + + Additional tools + @@ -44,7 +212,7 @@ 0 0 - 358 + 340 24 @@ -52,22 +220,63 @@ Account + + + Devices + Help + + - + + + false + + + Log-in + + + Not implemented yet. Sorry. + + + + + Manage App IDs + + + + + Manage certificates + + + + + Refresh device list + + + + + Donate + + + + + About + + From 2758a7b7af0b0882feac1ac9758db9b5529940e9 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sat, 30 Dec 2023 18:18:52 +0100 Subject: [PATCH 28/36] Fix nekojb ipa installation --- source/sideload/application.d | 7 ++++++- source/tools/sidestorepairingfile.d | 18 ++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/source/sideload/application.d b/source/sideload/application.d index 08afc37..9c8a842 100644 --- a/source/sideload/application.d +++ b/source/sideload/application.d @@ -48,7 +48,12 @@ class Application: Bundle { auto payloadFolder = tempPath.buildPath("Payload"); assertBundle(file.exists(payloadFolder), "No Payload folder!"); - auto apps = file.dirEntries(payloadFolder, file.SpanMode.shallow).array; + auto apps = file.dirEntries(payloadFolder, file.SpanMode.shallow) + .filter!( + folder => + folder[$ - 4..$] == ".app" || + folder[$ - 5..$] == ".app/" // It should not happen, but just in case + ).array; assertBundle(apps.length == 1, "No or too many application folder!"); path = apps[0]; diff --git a/source/tools/sidestorepairingfile.d b/source/tools/sidestorepairingfile.d index e44dabe..733d01f 100644 --- a/source/tools/sidestorepairingfile.d +++ b/source/tools/sidestorepairingfile.d @@ -64,23 +64,21 @@ class SideStoreTool: Tool { } break; case LOCKDOWN_E_USER_DENIED_PAIRING: - if (notify("You refused to trust the computer.", false)) { - return; - } - break; + notify("You refused to trust the computer.", false); + return; default: - if (notify("Unknown error, please check that the device is plugged correctly, unlocked and trusts the computer. (press OK to try again)")) { - return; - } - break; + notify("Unknown error, please check that the device is plugged correctly, unlocked and trusts the computer. (press OK to try again)"); + return; } } while (error != lockdownd_error_t.LOCKDOWN_E_SUCCESS); } string udid = device.udid(); - scope pairRecord = Plist.fromMemory(readPairRecord(udid)).dict(); + ubyte[] pairingFile = readPairRecord(udid); + log.debugF!"Pairing file fetched (is null: %s, length: %d)."(pairingFile == null, pairingFile == null ? 0 : pairingFile.length); + scope pairRecord = Plist.fromMemory(pairingFile).dict(); pairRecord["UDID"] = udid.pl; - log.debug_("Pairing file obtained"); + log.debug_("Pairing file ready."); string hostId = pairRecord["HostID"].str().native(); // string sessionId = lockdowndClient.startSession(hostId); From 3e816b896e74ab9a2cfc148bb60edc4bf1162999 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Tue, 2 Jan 2024 04:00:43 +0100 Subject: [PATCH 29/36] Add DependencyWindow to the Qt frontend --- frontends/common/windows/graphical_app.d | 11 +- frontends/qt/dub.json | 23 +- frontends/qt/resources/dependencieswindow.ui | 77 ++++ frontends/qt/resources/mainwindow.ui | 405 ++++++++++++------- frontends/qt/source/main.d | 22 +- frontends/qt/source/ui/dependencieswindow.d | 133 ++++++ frontends/qt/source/ui/mainwindow.d | 282 ++++++++++++- frontends/qt/source/ui/utils.d | 10 + source/app/package.d | 2 +- 9 files changed, 796 insertions(+), 169 deletions(-) create mode 100644 frontends/qt/resources/dependencieswindow.ui create mode 100644 frontends/qt/source/ui/dependencieswindow.d create mode 100644 frontends/qt/source/ui/utils.d diff --git a/frontends/common/windows/graphical_app.d b/frontends/common/windows/graphical_app.d index 427321a..ad81e62 100644 --- a/frontends/common/windows/graphical_app.d +++ b/frontends/common/windows/graphical_app.d @@ -4,10 +4,13 @@ import core.sys.windows.windef; public import core.sys.windows.winbase: SetUnhandledExceptionFilter; pragma(linkerDirective, "/SUBSYSTEM:WINDOWS"); -static if (__VERSION__ >= 2091) - pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); -else - pragma(linkerDirective, "/ENTRY:mainCRTStartup"); +version (Qt) +{ + static if (__VERSION__ >= 2091) + pragma(linkerDirective, "/ENTRY:wmainCRTStartup"); + else + pragma(linkerDirective, "/ENTRY:mainCRTStartup"); +} private class SegmentationFault: Throwable /+ Throwable since it should not be caught +/ { this(string file = __FILE__, size_t line = __LINE__) { diff --git a/frontends/qt/dub.json b/frontends/qt/dub.json index 2cc1da3..9e89a48 100644 --- a/frontends/qt/dub.json +++ b/frontends/qt/dub.json @@ -3,6 +3,7 @@ "targetType": "executable", "targetPath": "../../bin/", "targetName": "sideloader", + "versions": ["Qt"], "sourcePaths": [ "source/" @@ -18,25 +19,7 @@ "stringImportPaths": ["resources/"], - "libs-windows": [ - "Qt5Core", - "Qt5Gui", - "Qt5Widgets" - ], - - "libs-linux": [ - "Qt5Core", - "Qt5Gui", - "Qt5Widgets" - ], - "lflags-osx": [ - "-framework", - "QtCore", - "-framework", - "QtGui", - "-framework", - "QtWidgets", "-rpath", "@loader_path/../Frameworks" ], @@ -44,8 +27,8 @@ "dependencies": { "sideloader": { "path": "../../" }, "dqt": { - "repository": "git+https://github.com/Dadoum/dqt.git", - "version": "9034b54601834d86306c1e0bce53a99f5971c84a" + "repository": "git+https://github.com/tim-dlang/dqt.git", + "version": "6a44b55f3a3691da930cb9eefe2a745afe1b764d" } } } \ No newline at end of file diff --git a/frontends/qt/resources/dependencieswindow.ui b/frontends/qt/resources/dependencieswindow.ui new file mode 100644 index 0000000..7358176 --- /dev/null +++ b/frontends/qt/resources/dependencieswindow.ui @@ -0,0 +1,77 @@ + + + Dialog + + + + 0 + 0 + 441 + 99 + + + + Download required + + + + + + A ~130 MB download is required. This will require 5 MB of storage space on your computer. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/frontends/qt/resources/mainwindow.ui b/frontends/qt/resources/mainwindow.ui index 20cec31..7ded83d 100644 --- a/frontends/qt/resources/mainwindow.ui +++ b/frontends/qt/resources/mainwindow.ui @@ -21,6 +21,9 @@ + + QLayout::SetMinAndMaxSize + 8 @@ -33,176 +36,299 @@ 8 - + - - - false + + + + 0 + 0 + 0 - - false - - - false - - - - Informations - - - - - - true - - - - - - + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + true - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - Device model: + No device selected. - - - - - - Device name: - - - - - - - iOS version: - - - - - - - true + + Qt::AlignCenter - - - Sideload - - - - - - - - - - - ... - - - - - - - - - <span style=" color:#e01b24;"></span> - - - - - - - - - Bundle name: - - - - - - - Bundle identifier: - - - - - - - - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + - - - Qt::Vertical - - - - 20 - 40 - + + + true - - - - - + 0 - - true + + false - - + + false + + + + 0 + 0 + + + + Informations + + + + + + true + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + + Device model: + + + + + + + Device name: + + + + + + + iOS version: + + + + + + + + Sideload + + + + + + + + + + + ... + + + + + + + + + + + + + + + + + + + Bundle name: + + + + + + + true + + + true + + + + + + + Bundle identifier: + + + + + + + true + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + true + + + + + + + + + + false + + + Install + + + + + + + + Additional tools + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + - + - Install + TextLabel + + + Qt::AlignCenter + + + true - - - Additional tools - - - @@ -221,6 +347,7 @@ Account + diff --git a/frontends/qt/source/main.d b/frontends/qt/source/main.d index dd14f5b..ddee935 100644 --- a/frontends/qt/source/main.d +++ b/frontends/qt/source/main.d @@ -21,6 +21,7 @@ import slf4d.provider; import constants; import utils; +import ui.dependencieswindow; import ui.mainwindow; int main(string[] args) { @@ -47,9 +48,26 @@ int main(string[] args) { } configureLoggingProvider(loggingProvider); + version (Windows) { + string configurationPath = environment["AppData"]; + } else version (OSX) { + string configurationPath = "~/Library/Preferences/".expandTilde(); + } else { + string configurationPath = environment.get("XDG_CONFIG_DIR") + .orDefault("~/.config") + .expandTilde(); + } + configurationPath = configurationPath.buildPath(applicationName ~ "Qt"); + + auto log = getLogger(); + + log.info(versionStr); + scope qtApp = new QApplication(Runtime.cArgs.argc, Runtime.cArgs.argv); - auto w = new MainWindow(); - w.show(); + DependenciesWindow.ensureDeps(configurationPath, (device, adi) { + auto w = new MainWindow(configurationPath, device, adi); + w.show(); + }); return qtApp.exec(); } diff --git a/frontends/qt/source/ui/dependencieswindow.d b/frontends/qt/source/ui/dependencieswindow.d new file mode 100644 index 0000000..5b35c97 --- /dev/null +++ b/frontends/qt/source/ui/dependencieswindow.d @@ -0,0 +1,133 @@ +module ui.dependencieswindow; + +import core.stdcpp.new_: cpp_new; +import core.thread; + +import file = std.file; +import std.path; + +import slf4d; + +import provision; + +import qt.core.namespace; +import qt.core.object; +import qt.core.thread; +import qt.gui.event; +import qt.helpers; +import qt.widgets.dialog; +import qt.widgets.progressbar; +import qt.widgets.ui; +import qt.widgets.boxlayout; + +import app; + +alias DependenciesWindowUI = UIStruct!"dependencieswindow.ui"; + +class DependenciesWindow: QDialog { + mixin(Q_OBJECT_D); + + string configurationPath; + void delegate(Device, ADI) successCallback; + + DependenciesWindowUI* ui; + + this(string configurationPath, void delegate(Device, ADI) successCallback) { + this.configurationPath = configurationPath; + this.successCallback = successCallback; + + ui = cpp_new!DependenciesWindowUI(); + ui.setupUi(this); + + QObject.connect(this.signal!"finished", this.slot!"applyAction"); + QObject.connect(this.signal!"downloadFinished", this.slot!"executeCallback"); + } + + @QSignal final void downloadFinished() { mixin(Q_SIGNAL_IMPL_D); } + + @QSlot + final void executeCallback() { + auto log = getLogger(); + log.info("Download done."); + + scope provisioningData = initializeADI(configurationPath); + successCallback(provisioningData.device, provisioningData.adi); + } + + @QSlot + final void applyAction(int result) { + assert(QThread.currentThread() == this.thread()); + + if (result == 1) { + auto window = new class QDialog { + mixin(Q_OBJECT_D); + + bool canClose = false; + QProgressBar progressBar; + + this() { + super(); + + QVBoxLayout verticalLayout = cpp_new!QVBoxLayout(this); + progressBar = cpp_new!QProgressBar(); + progressBar.setRange(0, 100); + verticalLayout.addWidget(progressBar); + this.setWindowModality(WindowModality.ApplicationModal); + + QObject.connect(this.signal!"progressMade", this.slot!"updateProgressBar"); + } + + extern(C++) override void closeEvent(QCloseEvent event) { + if (!canClose) { + event.ignore(); + return; + } + super.closeEvent(event); + } + + @QSignal final void progressMade(int progressPercent) { mixin(Q_SIGNAL_IMPL_D); } + + @QSlot + final void updateProgressBar(int progressPercent) { + if (progressPercent < 100) { + progressBar.setValue(progressPercent); + } else { + progressBar.setRange(0, 0); + } + } + }; + window.show(); + new Thread({ + auto log = getLogger(); + log.info("Downloading Apple's APK."); + auto succeeded = downloadAndInstallDeps(configurationPath, (progress) { + window.progressMade(cast(int) (progress * 100)); + return false; + }); + + if (succeeded) { + log.info("Download successful."); + this.downloadFinished(); + } else { + log.info("Download failed :("); + } + window.canClose = true; + window.close(); + }).start(); + } + } + + static void ensureDeps(string configurationPath, void delegate(Device, ADI) successCallback) { + if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { + auto log = getLogger(); + log.info("Download required."); + auto dependenciesWindow = new DependenciesWindow(configurationPath, successCallback); + dependenciesWindow.show(); + } else { + scope provisioningData = initializeADI(configurationPath); + successCallback(provisioningData.device, provisioningData.adi); + } + } +} + + diff --git a/frontends/qt/source/ui/mainwindow.d b/frontends/qt/source/ui/mainwindow.d index 305cd3d..00749e1 100644 --- a/frontends/qt/source/ui/mainwindow.d +++ b/frontends/qt/source/ui/mainwindow.d @@ -2,26 +2,302 @@ module ui.mainwindow; import core.stdcpp.new_: cpp_new; +import std.algorithm; +import std.conv; +import file = std.file; +import std.format; +import std.process; + +import slf4d; + +import plist; + +import provision; + import qt.config; import qt.core.coreevent; +import qt.core.object; +import qt.core.objectdefs; import qt.core.string; +import qt.core.thread; import qt.core.translator; +import qt.core.variant; import qt.helpers; import qt.widgets.action; +import qt.widgets.combobox; +import qt.widgets.filedialog; +import qt.widgets.label; +import qt.widgets.lineedit; import qt.widgets.mainwindow; +import qt.widgets.messagebox; +import qt.widgets.pushbutton; +import qt.widgets.stackedwidget; import qt.widgets.ui; import qt.widgets.widget; +import imobiledevice; + +import constants; +import sideload; +import tools; +import utils; + +import ui.utils; + alias MainWindowUI = UIStruct!"mainwindow.ui"; class MainWindow: QMainWindow { + mixin(Q_OBJECT_D); + MainWindowUI* ui; - mixin(Q_OBJECT_D); - this() { - setWindowTitle(QString("Sideloader")); + iDevice selectedDevice; + LockdowndClient lockdowndClient; + Application selectedApplication; + + this(string configurationPath, Device device, ADI adi) { ui = cpp_new!MainWindowUI(); ui.setupUi(this); + + auto log = getLogger(); + QObject.connect(this.signal!"deviceAdded", this.slot!"addDevice"); + QObject.connect(this.signal!"deviceRemoved", this.slot!"removeDevice"); + QObject.connect(ui.deviceComboBox.signal!"currentIndexChanged", this.slot!"refreshView"); + QObject.connect(ui.actionRefresh_device_list.signal!"triggered", this.slot!"refreshDevices"); + QObject.connect(ui.actionDonate.signal!"triggered", delegate() => browse("https://github.com/sponsors/Dadoum")); + QObject.connect(ui.ipaLine.signal!"editingFinished", this.slot!"checkApplication"); + QObject.connect( + ui.actionAbout.signal!"triggered", + delegate() => + QMessageBox.about( + this, + *cpp_new!QString("About Sideloader"), + *cpp_new!QString(format!rawAboutText(versionStr, "Qt")) + ) + ); + QObject.connect( + ui.selectIpaButton.signal!"clicked", + delegate() { + QString filename = + QFileDialog.getOpenFileName( + this, + *cpp_new!QString("Open application"), + globalInitVar!QString, + *cpp_new!QString("iOS application bundle (*.ipa)") + ); + ui.ipaLine.setText(filename); + checkApplication(); + } + ); + + ui.bundleInfos.hide(); + iDevice.subscribeEvent((ref const(iDeviceEvent) event) { + with (iDeviceEventType) switch (event.event) { + case add: + deviceAdded(*cpp_new!QString(event.udid)); + log.infoF!"Device with UDID %s has been added."(event.udid); + break; + case remove: + deviceRemoved(*cpp_new!QString(event.udid)); + log.infoF!"Device with UDID %s has been removed."(event.udid); + break; + case paired: + log.infoF!"Device with UDID %s has been paired."(event.udid); + break; + default: + log.infoF!"Device with UDID %s has been ???? (%s)."(event.udid, event.event); + break; + } + }); + // auto stackedWidget = new QStackedWidget(); + // ui.tabWidget.sizePolicy().setRetainSizeWhenHidden(true); + // ui.tabWidget.setVisible(false); + } + + @QSignal final void deviceAdded(ref const(QString) udid) { mixin(Q_SIGNAL_IMPL_D); } + @QSignal final void deviceRemoved(ref const(QString) udid) { mixin(Q_SIGNAL_IMPL_D); } + + // @QSignal final bool showDialog(ref const(QMessageBox) udid) { mixin(Q_SIGNAL_IMPL_D); } + + @QSlot + final void addDevice(ref const(QString) udid) { + assert(QThread.currentThread() == this.thread()); + + QComboBox deviceComboBox = ui.deviceComboBox; + if (deviceComboBox.findData(QVariant(udid)) != -1) { + return; + } + + string udidStr = udid.toConstWString().to!string(); + scope device = new iDevice(udidStr); + + string deviceName = "???"; + try { + scope lockdown = new LockdowndClient(device, "sideloader.name-fetcher"); + deviceName = lockdown.deviceName(); + } catch (iMobileDeviceException!lockdownd_error_t) { } + + auto deviceDisplayName = cpp_new!QString(format!"%s (%s)"(deviceName, udidStr)); + + deviceComboBox.addItem(*deviceDisplayName, QVariant(udid)); + } + + @QSlot + final void removeDevice(ref const(QString) udid) { + assert(QThread.currentThread() == this.thread()); + if (iDevice.deviceList().canFind!(elem => elem.udid == udid.toConstWString().to!string())) { + return; + } + + QComboBox deviceComboBox = ui.deviceComboBox; + auto deviceIndex = deviceComboBox.findData(QVariant(udid)); + assert(deviceIndex != -1); + deviceComboBox.removeItem(deviceIndex); + } + + @QSlot + final void refreshDevices() { + assert(QThread.currentThread() == this.thread()); + QComboBox deviceComboBox = ui.deviceComboBox; + deviceComboBox.clear(); + foreach (deviceInfo; iDevice.deviceList()) { + deviceAdded(*cpp_new!QString(deviceInfo.udid)); + } + } + + @QSlot + final void refreshView(int index) { + if (index == -1) { + ui.stackedWidget.setCurrentIndex(0); + return; + } + + if (selectedDevice) { + object.destroy(selectedDevice); + } + if (lockdowndClient) { + object.destroy(lockdowndClient); + } + + QComboBox deviceComboBox = ui.deviceComboBox; + + string deviceUdid = + deviceComboBox + .itemData(index) + .toString() + .toConstWString() + .to!string(); + + selectedDevice = new iDevice(deviceUdid); + + try { + lockdowndClient = new LockdowndClient(selectedDevice, "sideloader.device-info"); + Plist deviceInfo = lockdowndClient[null, null]; + + string deviceName = deviceInfo["DeviceName"].str().native(); + string modelName = format!"%s (%s)"( + deviceInfo["ProductType"].str().native(), + deviceInfo["HardwareModel"].str().native() + ); + string iosVersion = format!"%s (%s)"( + deviceInfo["ProductVersion"].str().native(), + deviceInfo["BuildVersion"].str().native() + ); + + ui.nameLine.setText(*cpp_new!QString(deviceName)); + ui.modelLine.setText(*cpp_new!QString(modelName)); + ui.versionLine.setText(*cpp_new!QString(iosVersion)); + + ui.additionalToolsLayout.clearLayout(); + + foreach (tool; toolList(selectedDevice)) { + auto button = cpp_new!QPushButton(QString(tool.name)); + auto toolDiag = tool.diagnostic; + button.setEnabled(tool.diagnostic == null); + if (toolDiag) { + button.setToolTip(*cpp_new!QString(toolDiag)); + } + + QObject.connect(button.signal!"clicked", () => tool.run((message, canCancel) { + alias StandardButton = QMessageBox.StandardButton; + alias StandardButtons = QMessageBox.StandardButtons; + + StandardButton button = QMessageBox.question( + this, + *cpp_new!QString(tool.name), + *cpp_new!QString(message), + StandardButtons(StandardButton.Ok | (canCancel ? StandardButton.Cancel : StandardButton.NoButton)) + ); + + return button == StandardButton.Cancel; + })); + + ui.additionalToolsLayout.addWidget(button); + } + // ui.tabWidget.setCurrentIndex(0); + ui.stackedWidget.setCurrentIndex(1); + } catch (iMobileDeviceException!lockdownd_error_t ex) { + lockdowndClient = null; + string message; + with (lockdownd_error_t) switch (ex.underlyingError) { + case LOCKDOWN_E_PASSWORD_PROTECTED: + message = "Please unlock your phone."; + break; + case LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING: + message = "Please trust the computer."; + break; + case LOCKDOWN_E_USER_DENIED_PAIRING: + message = "The computer has not been trusted."; + break; + default: + message = format!"Can't connect to the device (%d).\nTry to plug the device again, unlock it and refresh."(ex.underlyingError); + break; + } + + ui.deviceConnectionErrorLabel.setText(*cpp_new!QString(format!"%s\n(refresh to try again)"(message))); + ui.stackedWidget.setCurrentIndex(2); + } + } + + @QSlot + void checkApplication() { + void setErrorLabel(string s) { + ui.appParsingErrorLabel.setText(*cpp_new!QString(format!`%s`(s))); + } + + string ipaFile = + ui.ipaLine.text() + .toConstWString() + .to!string(); + + ui.bundleInfos.setVisible(false); + ui.installButton.setEnabled(false); + selectedApplication = null; + + if (!file.exists(ipaFile)) { + setErrorLabel("No such file or directory"); + return; + } + + if (!file.isFile(ipaFile)) { + setErrorLabel("Is not a file"); + return; + } + + auto log = getLogger(); + + try { + Application app = new Application(ipaFile); + ui.bundleNameLine.setText(*cpp_new!QString(app.appInfo["CFBundleName"].str().native())); + ui.bundleIdentifierLine.setText(*cpp_new!QString(app.appInfo["CFBundleIdentifier"].str().native())); + selectedApplication = app; + setErrorLabel(""); + ui.bundleInfos.setVisible(true); + ui.installButton.setEnabled(true); + } catch (InvalidBundleException ex) { + log.infoF!"%s"(ex); + setErrorLabel(ex.msg); + } } } diff --git a/frontends/qt/source/ui/utils.d b/frontends/qt/source/ui/utils.d new file mode 100644 index 0000000..2501fc1 --- /dev/null +++ b/frontends/qt/source/ui/utils.d @@ -0,0 +1,10 @@ +module ui.utils; + +import qt.widgets.layout; +import qt.widgets.layoutitem; + +void clearLayout(QLayout layout) +{ + while (QLayoutItem item = layout.takeAt(0)) + destroy(item); +} diff --git a/source/app/package.d b/source/app/package.d index d27e701..c83060f 100644 --- a/source/app/package.d +++ b/source/app/package.d @@ -46,7 +46,7 @@ bool downloadAndInstallDeps(string configurationPath, bool delegate(float progre string libPath = configurationPath.buildPath("lib"); if (!file.exists(libPath)) { - file.mkdir(libPath); + file.mkdirRecurse(libPath); } version (X86_64) { From cb8e05edfe874b486ce310902a22cbafe1b8db45 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sat, 2 Mar 2024 19:44:51 +0100 Subject: [PATCH 30/36] Force rebuild From 545820a4198a187883483d678a53d0d4587015f7 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Sat, 2 Mar 2024 19:59:32 +0100 Subject: [PATCH 31/36] Attempt to fix Windows builds with a simple change. It's not going to be that easy unfortunately. --- .github/workflows/build-cli.yml | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 56062ae..1a4a4a5 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -256,7 +256,7 @@ jobs: ${{github.workspace}}/bin/sideloader-cli-macOS-arm64 build-windows-x86_64: - runs-on: ubuntu-22.04 + runs-on: windows-2019 steps: - uses: actions/checkout@v3 @@ -267,31 +267,31 @@ jobs: with: compiler: ldc-1.33.0 - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld 7zip - - - name: Set-up Windows cross-compilation - run: | - mkdir -p $HOME/.ldc/ - cat << EOF | tee $HOME/.ldc/ldc2.conf - "x86_64-.*-windows-msvc": - { - // default switches injected before all explicit command-line switches - switches = [ - "-defaultlib=phobos2-ldc,druntime-ldc", - ]; - // default switches appended after all explicit command-line switches - post-switches = [ - "-I$HOME/ldc2-1.33.0-windows-x64/import", - ]; - // default directories to be searched for libraries when linking - lib-dirs = [ - "$HOME/ldc2-1.33.0-windows-x64/lib", - ]; - }; - EOF - curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-windows-x64.7z - 7z x ./ldc2-1.33.0-windows-x64.7z -o$HOME + # - name: Install dependencies + # run: sudo apt-get update && sudo apt-get install -y libz-dev elfutils clang lld 7zip + # + # - name: Set-up Windows cross-compilation + # run: | + # mkdir -p $HOME/.ldc/ + # cat << EOF | tee $HOME/.ldc/ldc2.conf + # "x86_64-.*-windows-msvc": + # { + # // default switches injected before all explicit command-line switches + # switches = [ + # "-defaultlib=phobos2-ldc,druntime-ldc", + # ]; + # // default switches appended after all explicit command-line switches + # post-switches = [ + # "-I$HOME/ldc2-1.33.0-windows-x64/import", + # ]; + # // default directories to be searched for libraries when linking + # lib-dirs = [ + # "$HOME/ldc2-1.33.0-windows-x64/lib", + # ]; + # }; + # EOF + # curl -LO https://github.com/ldc-developers/ldc/releases/download/v1.33.0/ldc2-1.33.0-windows-x64.7z + # 7z x ./ldc2-1.33.0-windows-x64.7z -o$HOME - name: Write version file run: echo 'module version_string; enum versionStr = "Sideloader automated build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d From 1d07b470040daa09f1a8dd1fee6bb7559d53063f Mon Sep 17 00:00:00 2001 From: Dadoum <24679280+Dadoum@users.noreply.github.com> Date: Thu, 21 Mar 2024 05:59:08 +0000 Subject: [PATCH 32/36] Fix parsing of unnamed certificates --- source/server/developersession.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/developersession.d b/source/server/developersession.d index 7853d2c..5b5f27c 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -213,7 +213,7 @@ class DeveloperSession { certPlist["certificateId"].str().native(), certPlist["serialNumber"].str().native(), certPlist["certContent"].data().native(), - certPlist["machineName"].str().native(), + "machineName" in certPlist, ) ).array()), (DeveloperPortalError err) => DeveloperPortalResponse(err) From d17ab5413fdd66cce955c5804cece915d93545ef Mon Sep 17 00:00:00 2001 From: Dadoum <24679280+Dadoum@users.noreply.github.com> Date: Thu, 21 Mar 2024 06:03:18 +0000 Subject: [PATCH 33/36] Give a default null machine name --- source/server/developersession.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/developersession.d b/source/server/developersession.d index 5b5f27c..9fe597e 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -213,7 +213,7 @@ class DeveloperSession { certPlist["certificateId"].str().native(), certPlist["serialNumber"].str().native(), certPlist["certContent"].data().native(), - "machineName" in certPlist, + "machineName" in certPlist ? certPlist["certContent"].str().native() : "", ) ).array()), (DeveloperPortalError err) => DeveloperPortalResponse(err) From 0d10c8c3a283bce5309af303b4c2b8043c3865ea Mon Sep 17 00:00:00 2001 From: Dadoum <24679280+Dadoum@users.noreply.github.com> Date: Thu, 21 Mar 2024 06:08:44 +0000 Subject: [PATCH 34/36] Fix a dumb conversion issue in developersession --- source/server/developersession.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/developersession.d b/source/server/developersession.d index 9fe597e..7cb1464 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -213,7 +213,7 @@ class DeveloperSession { certPlist["certificateId"].str().native(), certPlist["serialNumber"].str().native(), certPlist["certContent"].data().native(), - "machineName" in certPlist ? certPlist["certContent"].str().native() : "", + "machineName" in certPlist.dict() ? certPlist["certContent"].str().native() : "", ) ).array()), (DeveloperPortalError err) => DeveloperPortalResponse(err) From 20bdf8ba02ff651515ce8e121488a912665df340 Mon Sep 17 00:00:00 2001 From: Dadoum <24679280+Dadoum@users.noreply.github.com> Date: Sun, 24 Mar 2024 12:00:13 +0000 Subject: [PATCH 35/36] Fix #25 --- source/server/developersession.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/developersession.d b/source/server/developersession.d index 7cb1464..9d25085 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -213,7 +213,7 @@ class DeveloperSession { certPlist["certificateId"].str().native(), certPlist["serialNumber"].str().native(), certPlist["certContent"].data().native(), - "machineName" in certPlist.dict() ? certPlist["certContent"].str().native() : "", + "machineName" in certPlist.dict() ? certPlist["machineName"].str().native() : "", ) ).array()), (DeveloperPortalError err) => DeveloperPortalResponse(err) From f620102d2d3cb7f87635775de5cea7b738807777 Mon Sep 17 00:00:00 2001 From: Dadoum Date: Fri, 29 Mar 2024 00:42:26 +0100 Subject: [PATCH 36/36] Add a UDID switch --- frontends/cli/source/install.d | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/frontends/cli/source/install.d b/frontends/cli/source/install.d index c8df8c9..67c80a3 100644 --- a/frontends/cli/source/install.d +++ b/frontends/cli/source/install.d @@ -21,6 +21,9 @@ struct InstallCommand @(PositionalArgument(0, "app path").Description("The path of the IPA file to sideload.")) string appPath; + @(NamedArgument("udid").Description("UDID of the device (if multiple are available).")) + string udid = null; + int opCall() { Application app = openApp(appPath); @@ -39,7 +42,22 @@ struct InstallCommand return 1; } - string udid = iDevice.deviceList()[0].udid; + auto devices = iDevice.deviceList(); + string udid = this.udid; + if (!udid) { + if (devices.length == 1) { + udid = devices[0].udid; + } else { + if (!devices.length) { + log.error("No device connected."); + return 1; + } + if (!this.udid) { + log.error("Multiple devices are connected. Please select one with --udid."); + } + } + } + log.infoF!"Initiating connection the device (UUID: %s)"(udid); auto device = new iDevice(udid); Bar progressBar = new Bar();