mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-14 17:47:13 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3daf494f | ||
|
|
4f7add046c | ||
|
|
61ec9af5e3 | ||
|
|
412dc92b10 | ||
|
|
04dab3dcd6 | ||
|
|
c51f885253 |
@@ -0,0 +1,428 @@
|
|||||||
|
name: Windows MSI/MSIX Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- 'release/**'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows_msi_build:
|
||||||
|
runs-on: windows-2022
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [x64]
|
||||||
|
arch: [x64]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.14.3'
|
||||||
|
architecture: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
New-Item -Name "deps" -ItemType "Directory" -Force | Out-Null
|
||||||
|
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install cffi
|
||||||
|
python -m pip install zstandard
|
||||||
|
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
function Download-WithRetry {
|
||||||
|
param(
|
||||||
|
[string]$Url,
|
||||||
|
[string]$OutFile,
|
||||||
|
[int]$MaxAttempts = 5,
|
||||||
|
[int]$InitialDelaySeconds = 2
|
||||||
|
)
|
||||||
|
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $Url -OutFile $OutFile -ErrorAction Stop
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if ($attempt -eq $MaxAttempts) {
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds ($InitialDelaySeconds * [math]::Pow(2, $attempt - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The existing solution includes installer.vcxproj, so Inno Setup is
|
||||||
|
# still required to build the unmodified solution. This workflow does
|
||||||
|
# not publish that EXE; it packages the same staged rel tree as MSI/MSIX.
|
||||||
|
Download-WithRetry -Url https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe -OutFile deps\innosetup-unicode.exe
|
||||||
|
& deps\innosetup-unicode.exe /VERYSILENT | Out-Null
|
||||||
|
|
||||||
|
# Use the same GTK runtime as the existing Windows EXE workflow.
|
||||||
|
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.20.0-alpha.1/GTK3_Gvsbuild_zoitechat-2.20.0-alpha.1_x64.zip -OutFile deps\gtk-${{ matrix.arch }}.zip
|
||||||
|
Expand-Archive -LiteralPath deps\gtk-${{ matrix.arch }}.zip -DestinationPath C:\gtk-build\gtk\x64\release -Force
|
||||||
|
|
||||||
|
# The GTK3 bundle does not contain librsvg. Restore the renderer,
|
||||||
|
# pixbuf loader, and license from the matching GTK4 bundle. Do not
|
||||||
|
# replace the rest of the GTK3 runtime with GTK4 libraries.
|
||||||
|
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.20.0-alpha.1/GTK4_Gvsbuild_zoitechat-2.20.0-alpha.1_x64.zip -OutFile deps\svg-runtime.zip
|
||||||
|
if ((Get-FileHash deps\svg-runtime.zip -Algorithm SHA256).Hash -ne 'fb657f9ebae3a6807defb15e66091d643a703ee66cb37488e3e11e5646f45adb') {
|
||||||
|
throw 'Unexpected SVG runtime archive checksum'
|
||||||
|
}
|
||||||
|
$svgArchive = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path deps\svg-runtime.zip))
|
||||||
|
try {
|
||||||
|
foreach ($name in @('bin/rsvg-2-2.dll', 'lib/gdk-pixbuf-2.0/2.10.0/loaders/pixbufloader_svg.dll', 'share/doc/librsvg/COPYING.LIB')) {
|
||||||
|
$entry = $svgArchive.GetEntry($name)
|
||||||
|
if ($null -eq $entry) { throw "Missing SVG runtime file: $name" }
|
||||||
|
$destination = Join-Path 'C:\gtk-build\gtk\x64\release' $name
|
||||||
|
New-Item -ItemType Directory -Path (Split-Path $destination) -Force | Out-Null
|
||||||
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destination, $true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { $svgArchive.Dispose() }
|
||||||
|
|
||||||
|
# Ensure SVG remains loadable before compiling or packaging.
|
||||||
|
& C:\gtk-build\gtk\x64\release\bin\gdk-pixbuf-query-loaders.exe --update-cache
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'Could not generate the pixbuf loader cache' }
|
||||||
|
if (-not (Select-String -LiteralPath 'C:\gtk-build\gtk\x64\release\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache' -SimpleMatch '"svg"')) {
|
||||||
|
throw 'SVG loader could not be loaded with the GTK3 runtime'
|
||||||
|
}
|
||||||
|
|
||||||
|
Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-hicolor-icon-theme-0.18-1-any.pkg.tar.zst -OutFile deps\hicolor-icon-theme.pkg.tar.zst
|
||||||
|
python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\hicolor-icon-theme.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith('mingw64/share/icons/hicolor/')];tf.close();reader.close();f.close()"
|
||||||
|
|
||||||
|
Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-libarchive-3.8.6-1-any.pkg.tar.zst -OutFile deps\libarchive.pkg.tar.zst
|
||||||
|
python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\libarchive.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith(('mingw64/include/archive','mingw64/lib/libarchive','mingw64/bin/libarchive'))];tf.close();reader.close();f.close()"
|
||||||
|
|
||||||
|
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\share\icons\hicolor) {
|
||||||
|
New-Item -Path C:\gtk-build\gtk\x64\release\share\icons -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\share\icons\hicolor -Destination C:\gtk-build\gtk\x64\release\share\icons\hicolor -Recurse -Force
|
||||||
|
}
|
||||||
|
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\include) {
|
||||||
|
New-Item -Path C:\gtk-build\gtk\x64\release\include -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\include\archive* -Destination C:\gtk-build\gtk\x64\release\include -Recurse -Force
|
||||||
|
}
|
||||||
|
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\lib) {
|
||||||
|
New-Item -Path C:\gtk-build\gtk\x64\release\lib -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\lib\libarchive* -Destination C:\gtk-build\gtk\x64\release\lib -Force
|
||||||
|
}
|
||||||
|
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\bin) {
|
||||||
|
New-Item -Path C:\gtk-build\gtk\x64\release\bin -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\bin\libarchive*.dll -Destination C:\gtk-build\gtk\x64\release\bin -Force
|
||||||
|
}
|
||||||
|
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64) {
|
||||||
|
Remove-Item -Path C:\gtk-build\gtk\x64\release\mingw64 -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/gendef20260315.7z -OutFile deps\gendef.7z
|
||||||
|
& 7z.exe x deps\gendef.7z -oC:\gtk-build
|
||||||
|
|
||||||
|
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/WinSparkle-20260315.7z -OutFile deps\WinSparkle.7z
|
||||||
|
& 7z.exe x deps\WinSparkle.7z -oC:\gtk-build\WinSparkle
|
||||||
|
|
||||||
|
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/perl-5.42.0.1-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z
|
||||||
|
& 7z.exe x deps\perl-${{ matrix.arch }}.7z -oC:\gtk-build\perl-5.42.0.1\${{ matrix.platform }}
|
||||||
|
|
||||||
|
$pyRoot = $env:pythonLocation
|
||||||
|
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
|
||||||
|
|
||||||
|
foreach ($pyDir in @("C:\gtk-build\python-3.14.3", "C:\gtk-build\python-3.14")) {
|
||||||
|
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
|
||||||
|
$target = Join-Path $pyDir "${{ matrix.platform }}"
|
||||||
|
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||||
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.8.10'
|
||||||
|
architecture: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Prepare Python 3.8
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install cffi
|
||||||
|
|
||||||
|
$pyRoot = $env:pythonLocation
|
||||||
|
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
|
||||||
|
|
||||||
|
foreach ($pyDir in @("C:\gtk-build\python-3.8.10", "C:\gtk-build\python-3.8")) {
|
||||||
|
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
|
||||||
|
$target = Join-Path $pyDir "${{ matrix.platform }}"
|
||||||
|
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||||
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||||
|
|
||||||
|
set "PYTHON_DIR=C:\gtk-build\python-3.14.3\${{ matrix.platform }}"
|
||||||
|
if not exist "%PYTHON_DIR%\libs\python314.lib" (
|
||||||
|
echo Missing %PYTHON_DIR%\libs\python314.lib
|
||||||
|
dir "%PYTHON_DIR%\libs"
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "PYTHON38_DIR=C:\gtk-build\python-3.8\${{ matrix.platform }}"
|
||||||
|
if not exist "%PYTHON38_DIR%\libs\python38.lib" (
|
||||||
|
echo Missing %PYTHON38_DIR%\libs\python38.lib
|
||||||
|
dir "%PYTHON38_DIR%\libs"
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not exist "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" (
|
||||||
|
echo Missing %PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "LIB=%PYTHON_DIR%\libs;%LIB%"
|
||||||
|
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
|
||||||
|
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }"
|
||||||
|
|
||||||
|
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\plugins\hcpython38.dll" (
|
||||||
|
echo hcpython38.dll was not built
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\_cffi_backend.cp38*.pyd" (
|
||||||
|
echo _cffi_backend for Python 3.8 was not staged for the installer
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Master added native emoji font staging and tests after the release
|
||||||
|
# branch split. Run these before packaging so MSI/MSIX use verified files.
|
||||||
|
- name: Test emoji catalog, font coverage and picker
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||||
|
set "PATH=%CD%\..\zoitechat-build\x64\rel;%PATH%"
|
||||||
|
set "GDK_PIXBUF_MODULE_FILE=%CD%\..\zoitechat-build\x64\rel\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache"
|
||||||
|
set "ZOITECHAT_EMOJI_FONT=%CD%\..\zoitechat-build\x64\rel\share\fonts\zoitechat\NotoColorEmoji.ttf"
|
||||||
|
fc /b data\fonts\NotoColorEmoji.ttf "%ZOITECHAT_EMOJI_FONT%"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
for %%T in (data font ui) do (
|
||||||
|
msbuild win32\emoji-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64 /p:EmojiTestKind=%%T
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: Install WiX Toolset
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
# Keep this pinned. WiX v6 introduced OSMF terms and WiX v7 adds
|
||||||
|
# explicit EULA acceptance; do not silently accept new terms in CI.
|
||||||
|
dotnet tool install --tool-path .wix wix --version 5.0.2
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'Failed to install WiX Toolset 5.0.2' }
|
||||||
|
& .\.wix\wix.exe --version
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'WiX Toolset could not be executed' }
|
||||||
|
|
||||||
|
- name: Build MSI
|
||||||
|
id: build_msi
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$rawVersion = (Get-Content VERSION -Raw).Trim()
|
||||||
|
if ($rawVersion -notmatch '^(\d+)\.(\d+)\.(\d+)') {
|
||||||
|
throw "VERSION '$rawVersion' does not begin with an MSI-compatible major.minor.patch version"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep the full development version in the filename, while MSI's
|
||||||
|
# ProductVersion is numeric (e.g. 2.20.0-dev -> 2.20.0).
|
||||||
|
$msiVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3])"
|
||||||
|
$safeVersion = $rawVersion -replace '[^0-9A-Za-z._-]', '-'
|
||||||
|
$sourceDir = (Resolve-Path "..\zoitechat-build\${{ matrix.platform }}\rel").Path
|
||||||
|
$outputPath = Join-Path $PWD "ZoiteChat-${safeVersion}_${{ matrix.arch }}.msi"
|
||||||
|
|
||||||
|
if (-not (Test-Path (Join-Path $sourceDir 'zoitechat.exe'))) {
|
||||||
|
throw "Staged ZoiteChat executable is missing from $sourceDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
& .\.wix\wix.exe build win32\wix\zoitechat.wxs `
|
||||||
|
-arch x64 `
|
||||||
|
-d "SourceDir=$sourceDir" `
|
||||||
|
-d "ProductVersion=$msiVersion" `
|
||||||
|
-out $outputPath
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'WiX MSI build failed' }
|
||||||
|
|
||||||
|
if (-not (Test-Path $outputPath)) {
|
||||||
|
throw "Expected MSI was not created: $outputPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
"path=$outputPath" >> $env:GITHUB_OUTPUT
|
||||||
|
"filename=$(Split-Path $outputPath -Leaf)" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Test MSI administrative extraction
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$msi = '${{ steps.build_msi.outputs.path }}'
|
||||||
|
$adminImage = Join-Path $PWD 'msi-admin-image'
|
||||||
|
New-Item -ItemType Directory -Path $adminImage -Force | Out-Null
|
||||||
|
|
||||||
|
$arguments = "/a `"$msi`" /qn TARGETDIR=`"$adminImage`""
|
||||||
|
$process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
|
||||||
|
if ($process.ExitCode -ne 0) {
|
||||||
|
throw "MSI administrative extraction failed with exit code $($process.ExitCode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredFile in @(
|
||||||
|
'zoitechat.exe',
|
||||||
|
'hclua.dll',
|
||||||
|
'hcupd.dll',
|
||||||
|
'WinSparkle.dll',
|
||||||
|
'hcchecksum.dll',
|
||||||
|
'hcexec.dll',
|
||||||
|
'hcfishlim.dll',
|
||||||
|
'hcsysinfo.dll',
|
||||||
|
'system.png',
|
||||||
|
'hcperl.dll',
|
||||||
|
'hcpython3.dll',
|
||||||
|
'hcpython38.dll',
|
||||||
|
'NotoColorEmoji.ttf',
|
||||||
|
'OFL.txt'
|
||||||
|
)) {
|
||||||
|
$match = Get-ChildItem -Path $adminImage -Filter $requiredFile -File -Recurse | Select-Object -First 1
|
||||||
|
if ($null -eq $match) {
|
||||||
|
throw "Administrative image does not contain required normal-install file: $requiredFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||||
|
$match = Get-ChildItem -Path $adminImage -Filter $requiredPattern -File -Recurse | Select-Object -First 1
|
||||||
|
if ($null -eq $match) {
|
||||||
|
throw "Administrative image does not contain required Python support file: $requiredPattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||||
|
$match = Get-ChildItem -Path $adminImage -Filter $requiredPythonFile -File -Recurse |
|
||||||
|
Where-Object { $_.Directory.Name -eq 'python' } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $match) {
|
||||||
|
throw "Administrative image does not contain required Python bridge file: $requiredPythonFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($excludedFile in @(
|
||||||
|
'portable-mode',
|
||||||
|
'zoitechat-text.exe'
|
||||||
|
)) {
|
||||||
|
$match = Get-ChildItem -Path $adminImage -Filter $excludedFile -File -Recurse | Select-Object -First 1
|
||||||
|
if ($null -ne $match) {
|
||||||
|
throw "MSI incorrectly contains Inno custom/portable-only file: $excludedFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Build MSIX
|
||||||
|
id: build_msix
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$rawVersion = (Get-Content VERSION -Raw).Trim()
|
||||||
|
if ($rawVersion -notmatch '^(\d+)\.(\d+)\.(\d+)') {
|
||||||
|
throw "VERSION '$rawVersion' does not begin with a Store-compatible major.minor.build version"
|
||||||
|
}
|
||||||
|
|
||||||
|
$versionParts = @(
|
||||||
|
[int]$Matches[1],
|
||||||
|
[int]$Matches[2],
|
||||||
|
[int]$Matches[3]
|
||||||
|
)
|
||||||
|
if ($versionParts[0] -eq 0) {
|
||||||
|
throw 'The Microsoft Store requires a non-zero major package version.'
|
||||||
|
}
|
||||||
|
foreach ($part in $versionParts) {
|
||||||
|
if ($part -lt 0 -or $part -gt 65535) {
|
||||||
|
throw "MSIX version component '$part' is outside the Store-supported 0-65535 range."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Partner Center reserves the fourth field as 0. A -dev version
|
||||||
|
# is packaged as a valid 2.20.0.0, without changing VERSION.
|
||||||
|
$packageVersion = "$($versionParts[0]).$($versionParts[1]).$($versionParts[2]).0"
|
||||||
|
$safeVersion = $rawVersion -replace '[^0-9A-Za-z._-]', '-'
|
||||||
|
$sourceDir = (Resolve-Path "..\zoitechat-build\${{ matrix.platform }}\rel").Path
|
||||||
|
$outputPath = Join-Path $PWD "ZoiteChat-${safeVersion}_${{ matrix.arch }}.msix"
|
||||||
|
|
||||||
|
# Reserved ZoiteChat identity from the release branch. Adjust only
|
||||||
|
# if the Product identity page in Partner Center changes.
|
||||||
|
$identityName = 'deependtilde.ZoiteChat'
|
||||||
|
$publisher = 'CN=6C7766BB-6AC6-488B-A09E-E3F8E68E63E4'
|
||||||
|
$publisherDisplayName = 'deepend_tilde'
|
||||||
|
$artifactName = 'MSIX Store Package ${{ matrix.arch }}'
|
||||||
|
|
||||||
|
Write-Host "Building Store MSIX for identity: $identityName"
|
||||||
|
|
||||||
|
& .\win32\msix\build-msix.ps1 `
|
||||||
|
-SourceDir $sourceDir `
|
||||||
|
-OutputPath $outputPath `
|
||||||
|
-PackageVersion $packageVersion `
|
||||||
|
-IdentityName $identityName `
|
||||||
|
-Publisher $publisher `
|
||||||
|
-PublisherDisplayName $publisherDisplayName `
|
||||||
|
-SelfSign
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw 'MSIX build script failed'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $outputPath)) {
|
||||||
|
throw "Expected MSIX was not created: $outputPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
"path=$outputPath" >> $env:GITHUB_OUTPUT
|
||||||
|
"filename=$(Split-Path $outputPath -Leaf)" >> $env:GITHUB_OUTPUT
|
||||||
|
"package_version=$packageVersion" >> $env:GITHUB_OUTPUT
|
||||||
|
"store_ready=true" >> $env:GITHUB_OUTPUT
|
||||||
|
"artifact_name=$artifactName" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload MSI Installer
|
||||||
|
id: upload_msi
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: MSI Installer ${{ matrix.arch }}
|
||||||
|
path: ${{ steps.build_msi.outputs.path }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Attest MSI Installer (Artifact Attestation)
|
||||||
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-name: MSI Installer ${{ matrix.arch }}
|
||||||
|
subject-digest: sha256:${{ steps.upload_msi.outputs.artifact-digest }}
|
||||||
|
|
||||||
|
- name: Upload MSIX Package
|
||||||
|
id: upload_msix
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: ${{ steps.build_msix.outputs.artifact_name }}
|
||||||
|
path: ${{ steps.build_msix.outputs.path }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Attest MSIX Package (Artifact Attestation)
|
||||||
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-name: ${{ steps.build_msix.outputs.artifact_name }}
|
||||||
|
subject-digest: sha256:${{ steps.upload_msix.outputs.artifact-digest }}
|
||||||
+48
-5
@@ -33,10 +33,53 @@ Example:
|
|||||||
that is not visually indented
|
that is not visually indented
|
||||||
```
|
```
|
||||||
|
|
||||||
## Read the Docs theme skin
|
## HTML theme
|
||||||
|
|
||||||
The HTML output keeps the existing `basicstrap` Sphinx theme, then applies the
|
The `zoitechat` theme inherits Sphinx's built-in `basic` theme. It uses the
|
||||||
ZoiteChat main-site skin from `_static/css/zoitechat-docs.css`.
|
same compact layout, blue navigation, and system fonts as zoitechat.org.
|
||||||
|
Appearance starts in System mode on each page. Light and Dark are available
|
||||||
|
for the current page; no cookies or browser storage are used.
|
||||||
|
|
||||||
Brand navigation links are set in `conf.py` through `html_context`, and the
|
The theme's templates are in `_theme/zoitechat/`. Its readable CSS and
|
||||||
header logo uses `_static/zoitechat-logo.svg`.
|
JavaScript are in `_static/css/zoitechat-docs.css` and
|
||||||
|
`_static/js/zoitechat-docs.js`. No minification, JavaScript package manager,
|
||||||
|
external fonts, or CDN assets are required.
|
||||||
|
|
||||||
|
Navigation URLs, including `zoite_atlas_url`, are configured through
|
||||||
|
`html_context` in `conf.py`. The logo
|
||||||
|
is `_static/zoitechat-logo.svg`. Standard Sphinx search, indexes, source
|
||||||
|
links, anchors, translations, and previous/next links remain available.
|
||||||
|
The HTML output also works as locally installed documentation; project-site
|
||||||
|
links only access the network when clicked.
|
||||||
|
|
||||||
|
Build from the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sphinx-build -b html docs docs/_build/html
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a clean output directory when switching themes, so stale Basicstrap
|
||||||
|
assets from an earlier build are not included in a package.
|
||||||
|
|
||||||
|
## Debian packaging
|
||||||
|
|
||||||
|
The active theme does not use the bundled Basicstrap, Bootstrap, jQuery,
|
||||||
|
jQuery Cookie, Font Awesome, or icon fonts. Its font stack includes DejaVu
|
||||||
|
Sans and DejaVu Sans Mono; no font files are bundled by the theme.
|
||||||
|
|
||||||
|
Packagers may exclude `docs/_theme/basicstrap/` from the source package.
|
||||||
|
The default theme continues to build without that directory. No pre-minified
|
||||||
|
third-party assets need to be added or rebuilt for this theme.
|
||||||
|
|
||||||
|
Build with the distribution's `python3-sphinx`. After installing the HTML
|
||||||
|
into the package staging directory, use `dh_sphinxdoc` (or `dh --with
|
||||||
|
sphinxdoc`) to replace Sphinx's standard JavaScript files with system
|
||||||
|
symlinks. Include `${sphinxdoc:Depends}` in the package dependencies and
|
||||||
|
`${sphinxdoc:Built-Using}` where required for generated assets. The custom
|
||||||
|
`zoitechat-docs.js` remains a small, readable project source file.
|
||||||
|
|
||||||
|
A built-in theme is still selectable with `-D html_theme=classic` if a
|
||||||
|
packager needs a different layout. The old RST content has existing build
|
||||||
|
diagnostics; changing the theme does not suppress or alter them.
|
||||||
|
|
||||||
|
Shared website chrome is in `_static/css/zoitechat-site.css`, copied from the main site. Keep that readable stylesheet in sync when changing the shared header, navigation, typography, or colours.
|
||||||
|
|||||||
Vendored
+694
-2281
File diff suppressed because it is too large
Load Diff
Vendored
+526
@@ -0,0 +1,526 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/* Shared chrome copied from ZoiteChat/site assets/css/site.css (1df996a). */
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--bg: #fff;
|
||||||
|
--surface: #fff;
|
||||||
|
--chrome: #f1f2f3;
|
||||||
|
--chrome2: #e3e5e7;
|
||||||
|
--text: #25282c;
|
||||||
|
--muted: #58616b;
|
||||||
|
--line: #bdc3c9;
|
||||||
|
--soft-line: #dce0e4;
|
||||||
|
--blue: #215a9e;
|
||||||
|
--link: #174f92;
|
||||||
|
--selection: #e6effa;
|
||||||
|
--success: #236741;
|
||||||
|
--code: #f2f4f6;
|
||||||
|
--shadow: none;
|
||||||
|
--mono: Consolas,"Liberation Mono",monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme=dark] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #111416;
|
||||||
|
--surface: #191c1f;
|
||||||
|
--chrome: #25292d;
|
||||||
|
--chrome2: #30353a;
|
||||||
|
--text: #eef0f2;
|
||||||
|
--muted: #b2bbc4;
|
||||||
|
--line: #50575e;
|
||||||
|
--soft-line: #353c43;
|
||||||
|
--blue: #2865a8;
|
||||||
|
--link: #8dc0ff;
|
||||||
|
--selection: #233951;
|
||||||
|
--success: #92d5ab;
|
||||||
|
--code: #111518;
|
||||||
|
--shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: 15px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link);
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input, select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, a, input, select, summary {
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 3px solid var(--link);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: inset(50%);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: -100px;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 0 0 16px;
|
||||||
|
background: var(--surface);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand img {
|
||||||
|
width: 38px;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand strong {
|
||||||
|
font-size: 28px;
|
||||||
|
letter-spacing: -.8px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand small {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appearance {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
background: var(--chrome);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a, .nav-menu summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 6px 15px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a[aria-current=page] {
|
||||||
|
color: #fff;
|
||||||
|
border-bottom-color: var(--blue);
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a:hover, .nav-menu summary:hover {
|
||||||
|
background: var(--selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu summary::after {
|
||||||
|
content: "⌄";
|
||||||
|
margin-left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-panel {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 5;
|
||||||
|
min-width: 280px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-panel a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-panel a:hover {
|
||||||
|
background: var(--selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-panel span {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-main {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(30px,3.9vw,46px);
|
||||||
|
line-height: 1.16;
|
||||||
|
letter-spacing: -1.5px;
|
||||||
|
font-weight: 650;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.3;
|
||||||
|
letter-spacing: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
padding: 17px 0;
|
||||||
|
background: var(--surface);
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copy {
|
||||||
|
font-size: 11px;
|
||||||
|
max-width: 62ch;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px 14px;
|
||||||
|
max-width: 400px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:1000px) {
|
||||||
|
.navigation {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a, .nav-menu summary {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:650px) {
|
||||||
|
.topbar {
|
||||||
|
padding: 13px 16px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand strong {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand small {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand img {
|
||||||
|
width: 28px;
|
||||||
|
height: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appearance {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appearance>span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appearance select {
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a, .nav-menu summary {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 34px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
padding: 20px 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-panel {
|
||||||
|
position: fixed;
|
||||||
|
right: 10px;
|
||||||
|
left: 10px;
|
||||||
|
top: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion:reduce) {
|
||||||
|
* {
|
||||||
|
scroll-behavior: auto!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appearance select {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a:hover {
|
||||||
|
background: var(--selection);
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -.5px;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:1100px) {
|
||||||
|
.navigation {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:800px) {
|
||||||
|
.nav-links>a, .nav-menu summary {
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:600px) {
|
||||||
|
.topbar {
|
||||||
|
padding: 0 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand strong {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand img {
|
||||||
|
width: 32px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links>a, .nav-menu summary {
|
||||||
|
padding: 7px 9px;
|
||||||
|
min-height: 38px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
margin-top: 10px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
padding: 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme]) {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #111416;
|
||||||
|
--surface: #191c1f;
|
||||||
|
--chrome: #25292d;
|
||||||
|
--chrome2: #30353a;
|
||||||
|
--text: #eef0f2;
|
||||||
|
--muted: #b2bbc4;
|
||||||
|
--line: #50575e;
|
||||||
|
--soft-line: #353c43;
|
||||||
|
--blue: #2865a8;
|
||||||
|
--link: #8dc0ff;
|
||||||
|
--selection: #233951;
|
||||||
|
--success: #92d5ab;
|
||||||
|
--code: #111518;
|
||||||
|
--shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header, .site-main, .site-footer {
|
||||||
|
width: min(1040px, calc(100% - 48px));
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-main {
|
||||||
|
padding-block: 28px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header .topbar {
|
||||||
|
padding: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header .navigation {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.site-header, .site-main, .site-footer {
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-main {
|
||||||
|
padding-top: 23px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match the browser's heading defaults used by the main site. */
|
||||||
|
h2, h3 { font-weight: bold; }
|
||||||
|
h2 { margin-top: .83em; }
|
||||||
|
h3 { margin-block: 1em; }
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
(() => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const initialise = () => {
|
||||||
|
const picker = document.getElementById('theme-picker');
|
||||||
|
const preference = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
|
||||||
|
if (picker instanceof HTMLSelectElement) {
|
||||||
|
const applyTheme = () => {
|
||||||
|
const dark = picker.value === 'dark'
|
||||||
|
|| (picker.value === 'system' && preference.matches);
|
||||||
|
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
|
||||||
|
document.querySelectorAll('meta[name="theme-color"]').forEach((meta) => {
|
||||||
|
meta.setAttribute('content', dark ? '#111416' : '#ffffff');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Follow the OS on every visit, including browser back/forward navigation.
|
||||||
|
const resetTheme = () => {
|
||||||
|
picker.value = 'system';
|
||||||
|
applyTheme();
|
||||||
|
};
|
||||||
|
picker.addEventListener('change', applyTheme);
|
||||||
|
preference.addEventListener('change', applyTheme);
|
||||||
|
window.addEventListener('pageshow', resetTheme);
|
||||||
|
resetTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = document.getElementById('docs-navigation');
|
||||||
|
const compact = window.matchMedia('(max-width: 760px)');
|
||||||
|
if (navigation instanceof HTMLDetailsElement) {
|
||||||
|
const fitNavigation = () => { navigation.open = !compact.matches; };
|
||||||
|
compact.addEventListener('change', fitNavigation);
|
||||||
|
fitNavigation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initialise, { once: true });
|
||||||
|
} else {
|
||||||
|
initialise();
|
||||||
|
}
|
||||||
|
})();
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{# SPDX-License-Identifier: GPL-2.0-or-later #}
|
||||||
|
<nav class="global-toc" aria-label="{{ _('Documentation contents') }}">
|
||||||
|
<h2>{{ _('Contents') }}</h2>
|
||||||
|
{{ toctree(maxdepth=2, collapse=true, includehidden=true, titles_only=true) }}
|
||||||
|
</nav>
|
||||||
Vendored
+108
@@ -0,0 +1,108 @@
|
|||||||
|
{# SPDX-License-Identifier: GPL-2.0-or-later #}
|
||||||
|
{% extends "basic/layout.html" %}
|
||||||
|
{% set docs_root = root_doc|default(master_doc) %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ super() }}
|
||||||
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||||
|
<meta name="theme-color" content="#111416" media="(prefers-color-scheme: dark)">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body_tag %}<body id="top">{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<a class="skip-link" href="#main-content">{{ _('Skip to content') }}</a>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="topbar">
|
||||||
|
<a class="brand" href="{{ pathto(docs_root)|e }}">
|
||||||
|
<img src="{{ logo_url|default(pathto('_static/zoitechat-logo.svg', 1), true)|e }}" alt="" width="38" height="42">
|
||||||
|
<strong>{{ project|e }}</strong>
|
||||||
|
</a>
|
||||||
|
<label class="appearance" for="theme-picker">
|
||||||
|
<span>{{ _('Appearance') }}</span>
|
||||||
|
<select id="theme-picker" aria-label="{{ _('Documentation appearance') }}">
|
||||||
|
<option value="system" selected>{{ _('System') }}</option>
|
||||||
|
<option value="light">{{ _('Light') }}</option>
|
||||||
|
<option value="dark">{{ _('Dark') }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="navigation">
|
||||||
|
<nav class="nav-links" aria-label="{{ _('Primary') }}">
|
||||||
|
<a href="{{ zoite_site_url|e }}">{{ _('Home') }}</a>
|
||||||
|
<a href="{{ zoite_download_url|e }}">{{ _('Downloads') }}</a>
|
||||||
|
<a href="{{ zoite_addons_url|e }}">{{ _('Addons') }}</a>
|
||||||
|
<a href="{{ zoite_announcements_url|e }}">{{ _('Announcements') }}</a>
|
||||||
|
<a href="{{ pathto(docs_root)|e }}" aria-current="page">{{ _('Documentation') }}</a>
|
||||||
|
<a href="{{ zoite_atlas_url|e }}">{{ _('Atlas') }}</a>
|
||||||
|
<a href="{{ zoite_github_url|e }}" target="_blank" rel="noopener">{{ _('GitHub') }}</a>
|
||||||
|
<details class="nav-menu">
|
||||||
|
<summary>{{ _('IRC support') }}</summary>
|
||||||
|
<div class="nav-menu-panel" aria-label="{{ _('IRC support channels') }}">
|
||||||
|
<a href="{{ zoite_irc_url|e }}" target="_blank" rel="noopener"><strong>{{ _('Zoite IRC') }}</strong><span>{{ _('Official support') }} · #zoitechat</span></a>
|
||||||
|
<a href="{{ libera_irc_url|e }}" target="_blank" rel="noopener"><strong>{{ _('Libera.Chat') }}</strong><span>{{ _('Secondary support') }} · #zoitechat</span></a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block relbar1 %}{% endblock %}
|
||||||
|
{% block relbar2 %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="docs-layout{% if not render_sidebar %} no-sidebar{% endif %}">
|
||||||
|
{% if render_sidebar %}
|
||||||
|
<aside class="docs-sidebar" aria-label="{{ _('Documentation navigation') }}">
|
||||||
|
<details class="docs-navigation" id="docs-navigation" open>
|
||||||
|
<summary>{{ _('Documentation') }}</summary>
|
||||||
|
<div class="sidebar-content">
|
||||||
|
{% for sidebartemplate in sidebars %}
|
||||||
|
{% include sidebartemplate %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
|
<main class="docs-content" id="main-content" tabindex="-1">
|
||||||
|
<div class="docs-location">
|
||||||
|
<nav aria-label="{{ _('Breadcrumbs') }}">
|
||||||
|
<a href="{{ pathto(docs_root)|e }}">{{ _('Documentation') }}</a>
|
||||||
|
{% for parent in parents %}
|
||||||
|
<span aria-hidden="true">/</span> <a href="{{ parent.link|e }}">{{ parent.title }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% if pagename != docs_root %}<span aria-hidden="true">/</span> <span>{{ title|striptags|e }}</span>{% endif %}
|
||||||
|
</nav>
|
||||||
|
<span class="docs-version">{{ release|e }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
{% if prev or next %}
|
||||||
|
<nav class="page-relations" aria-label="{{ _('Related pages') }}">
|
||||||
|
<div>
|
||||||
|
{% if prev %}<small>{{ _('Previous') }}</small><a href="{{ prev.link|e }}" rel="prev">{{ prev.title }}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{% if next %}<small>{{ _('Next') }}</small><a href="{{ next.link|e }}" rel="next">{{ next.title }}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="footer-grid">
|
||||||
|
<p class="footer-copy">{{ project|e }} {{ _('documentation') }} · {{ release|e }}</p>
|
||||||
|
<nav class="footer-links" aria-label="{{ _('Footer navigation') }}">
|
||||||
|
<a href="{{ pathto(docs_root)|e }}">{{ _('Contents') }}</a>
|
||||||
|
<a href="{{ zoite_atlas_url|e }}">{{ _('Atlas') }}</a>
|
||||||
|
{% if hasdoc('search') %}<a href="{{ pathto('search')|e }}">{{ _('Search') }}</a>{% endif %}
|
||||||
|
{% if hasdoc('genindex') %}<a href="{{ pathto('genindex')|e }}">{{ _('Index') }}</a>{% endif %}
|
||||||
|
<a href="#top">{{ _('Back to top') }}</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{# SPDX-License-Identifier: GPL-2.0-or-later #}
|
||||||
|
{% if display_toc %}
|
||||||
|
<nav class="local-toc" aria-label="{{ _('On this page') }}">
|
||||||
|
<h2>{{ _('On this page') }}</h2>
|
||||||
|
{{ toc }}
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{# SPDX-License-Identifier: GPL-2.0-or-later #}
|
||||||
|
{% if pagename != "search" %}
|
||||||
|
<form class="docs-search" action="{{ pathto('search')|e }}" method="get" role="search">
|
||||||
|
<label for="docs-search-input">{{ _('Search documentation') }}</label>
|
||||||
|
<div class="search-inputs">
|
||||||
|
<input id="docs-search-input" type="search" name="q" placeholder="{{ _('Search documentation') }}" autocomplete="off" spellcheck="false">
|
||||||
|
<button type="submit">{{ _('Search') }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
[theme]
|
||||||
|
inherit = basic
|
||||||
|
stylesheet = basic.css
|
||||||
|
pygments_style = sphinx
|
||||||
|
sidebars = searchbox.html, globaltoc.html, localtoc.html, sourcelink.html
|
||||||
|
|
||||||
|
[options]
|
||||||
+8
-4
@@ -99,7 +99,7 @@ trim_footnote_reference_space = True
|
|||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'basicstrap'
|
html_theme = 'zoitechat'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
@@ -127,13 +127,16 @@ html_context = {
|
|||||||
'zoite_site_url': 'https://zoitechat.org/',
|
'zoite_site_url': 'https://zoitechat.org/',
|
||||||
'zoite_download_url': 'https://zoitechat.org/download.php',
|
'zoite_download_url': 'https://zoitechat.org/download.php',
|
||||||
'zoite_addons_url': 'https://zoitechat.org/addons.php',
|
'zoite_addons_url': 'https://zoitechat.org/addons.php',
|
||||||
|
'zoite_announcements_url': 'https://zoitechat.org/announcements.php',
|
||||||
|
'zoite_github_url': 'https://github.com/ZoiteChat/zoitechat',
|
||||||
|
'zoite_atlas_url': 'https://atlas.zoitechat.org/',
|
||||||
'zoite_irc_url': 'https://zoite.net/webchat/?join=%23zoitechat',
|
'zoite_irc_url': 'https://zoite.net/webchat/?join=%23zoitechat',
|
||||||
'libera_irc_url': 'https://web.libera.chat/#zoitechat',
|
'libera_irc_url': 'https://web.libera.chat/#zoitechat',
|
||||||
}
|
}
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
html_logo = 'zoitechat-logo.svg'
|
html_logo = '_static/zoitechat-logo.svg'
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
@@ -145,8 +148,9 @@ html_favicon = '_static/favicon.ico'
|
|||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
# Main-site visual skin for Read the Docs/Sphinx HTML output.
|
# Local, readable assets shared by hosted and offline HTML documentation.
|
||||||
html_css_files = ['css/zoitechat-docs.css']
|
html_css_files = ['css/zoitechat-docs.css', 'css/zoitechat-site.css']
|
||||||
|
html_js_files = ['js/zoitechat-docs.js']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
|||||||
@@ -590,6 +590,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_gtk3_theme", P_OFFSET (hex_gui_gtk3_theme), TYPE_STR},
|
{"gui_gtk3_theme", P_OFFSET (hex_gui_gtk3_theme), TYPE_STR},
|
||||||
{"text_indent", P_OFFINT (hex_text_indent), TYPE_BOOL},
|
{"text_indent", P_OFFINT (hex_text_indent), TYPE_BOOL},
|
||||||
{"text_max_indent", P_OFFINT (hex_text_max_indent), TYPE_INT},
|
{"text_max_indent", P_OFFINT (hex_text_max_indent), TYPE_INT},
|
||||||
|
{"text_wrap_align", P_OFFINT (hex_text_wrap_align), TYPE_INT},
|
||||||
{"text_max_lines", P_OFFINT (hex_text_max_lines), TYPE_INT},
|
{"text_max_lines", P_OFFINT (hex_text_max_lines), TYPE_INT},
|
||||||
{"text_replay", P_OFFINT (hex_text_replay), TYPE_BOOL},
|
{"text_replay", P_OFFINT (hex_text_replay), TYPE_BOOL},
|
||||||
{"text_search_case_match", P_OFFINT (hex_text_search_case_match), TYPE_BOOL},
|
{"text_search_case_match", P_OFFINT (hex_text_search_case_match), TYPE_BOOL},
|
||||||
@@ -881,6 +882,7 @@ load_default_config(void)
|
|||||||
prefs.hex_net_reconnect_delay = 10;
|
prefs.hex_net_reconnect_delay = 10;
|
||||||
prefs.hex_notify_timeout = 15;
|
prefs.hex_notify_timeout = 15;
|
||||||
prefs.hex_text_max_indent = 256;
|
prefs.hex_text_max_indent = 256;
|
||||||
|
prefs.hex_text_wrap_align = 0;
|
||||||
prefs.hex_text_max_lines = 5000;
|
prefs.hex_text_max_lines = 5000;
|
||||||
prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */
|
prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */
|
||||||
|
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ struct zoitechatprefs
|
|||||||
int hex_net_reconnect_delay;
|
int hex_net_reconnect_delay;
|
||||||
int hex_notify_timeout;
|
int hex_notify_timeout;
|
||||||
int hex_text_max_indent;
|
int hex_text_max_indent;
|
||||||
|
int hex_text_wrap_align;
|
||||||
int hex_text_max_lines;
|
int hex_text_max_lines;
|
||||||
int hex_url_grabber_limit;
|
int hex_url_grabber_limit;
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ static struct zoitechatprefs setup_prefs;
|
|||||||
static GtkWidget *cancel_button;
|
static GtkWidget *cancel_button;
|
||||||
static GtkWidget *font_dialog = NULL;
|
static GtkWidget *font_dialog = NULL;
|
||||||
static GtkWidget *setup_topicbar_multiline_toggle = NULL;
|
static GtkWidget *setup_topicbar_multiline_toggle = NULL;
|
||||||
|
static GtkWidget *setup_wrap_align_label = NULL;
|
||||||
|
static GtkWidget *setup_wrap_align_combo = NULL;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@@ -157,6 +159,14 @@ static const char *const langsmenu[] =
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const char *const wrapalignmenu[] =
|
||||||
|
{
|
||||||
|
N_("Start of line (default)"),
|
||||||
|
N_("Prefix (nick name)"),
|
||||||
|
N_("Message"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
static const setting appearance_settings[] =
|
static const setting appearance_settings[] =
|
||||||
{
|
{
|
||||||
{ST_HEADER, N_("General"),0,0,0},
|
{ST_HEADER, N_("General"),0,0,0},
|
||||||
@@ -172,6 +182,7 @@ static const setting appearance_settings[] =
|
|||||||
{ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(hex_text_indent), N_("Make nick names right-justified"),0,0},
|
{ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(hex_text_indent), N_("Make nick names right-justified"),0,0},
|
||||||
{ST_TOGGLE, N_ ("Show marker line"), P_OFFINTNL (hex_text_show_marker), N_ ("Insert a red line after the last read text."), 0, 0},
|
{ST_TOGGLE, N_ ("Show marker line"), P_OFFINTNL (hex_text_show_marker), N_ ("Insert a red line after the last read text."), 0, 0},
|
||||||
{ST_TOGGLE, N_("Keep search position when closing"), P_OFFINTNL(hex_text_search_keep_position), 0, 0, 0},
|
{ST_TOGGLE, N_("Keep search position when closing"), P_OFFINTNL(hex_text_search_keep_position), 0, 0, 0},
|
||||||
|
{ST_MENU, N_("Align wrapped lines under:"), P_OFFINTNL(hex_text_wrap_align), N_("Where wrapped lines start when nick names are not indented"), wrapalignmenu, 0},
|
||||||
|
|
||||||
{ST_HEADER, N_("Timestamps"),0,0,0},
|
{ST_HEADER, N_("Timestamps"),0,0,0},
|
||||||
{ST_TOGGLE, N_("Enable timestamps"), P_OFFINTNL(hex_stamp_text),0,0,1},
|
{ST_TOGGLE, N_("Enable timestamps"), P_OFFINTNL(hex_stamp_text),0,0,1},
|
||||||
@@ -857,6 +868,20 @@ setup_topicbar_inline_toggled_cb (GtkToggleButton *but, gpointer userdata)
|
|||||||
!gtk_toggle_button_get_active (but));
|
!gtk_toggle_button_get_active (but));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_text_indent_toggled_cb (GtkToggleButton *but, gpointer userdata)
|
||||||
|
{
|
||||||
|
gboolean sens = !gtk_toggle_button_get_active (but);
|
||||||
|
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
/* wrapped line alignment only applies without nick indentation */
|
||||||
|
if (setup_wrap_align_label)
|
||||||
|
gtk_widget_set_sensitive (setup_wrap_align_label, sens);
|
||||||
|
if (setup_wrap_align_combo)
|
||||||
|
gtk_widget_set_sensitive (setup_wrap_align_combo, sens);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
||||||
{
|
{
|
||||||
@@ -867,6 +892,9 @@ setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
|||||||
setup_get_int (&setup_prefs, set));
|
setup_get_int (&setup_prefs, set));
|
||||||
g_signal_connect (G_OBJECT (wid), "toggled",
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_text_indent))
|
||||||
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
|
G_CALLBACK (setup_text_indent_toggled_cb), NULL);
|
||||||
if (set->tooltip)
|
if (set->tooltip)
|
||||||
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
||||||
setup_table_attach (tab, wid, 4, 5, row, row + 1, TRUE, FALSE,
|
setup_table_attach (tab, wid, 4, 5, row, row + 1, TRUE, FALSE,
|
||||||
@@ -1279,6 +1307,16 @@ setup_create_menu (GtkWidget *table, int row, const setting *set)
|
|||||||
|
|
||||||
setup_table_attach (table, cbox, 3, 6, row, row + 1, TRUE, FALSE,
|
setup_table_attach (table, cbox, 3, 6, row, row + 1, TRUE, FALSE,
|
||||||
SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
|
SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
|
||||||
|
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_text_wrap_align))
|
||||||
|
{
|
||||||
|
setup_wrap_align_label = wid;
|
||||||
|
setup_wrap_align_combo = cbox;
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (wid), (gpointer *)&setup_wrap_align_label);
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (cbox), (gpointer *)&setup_wrap_align_combo);
|
||||||
|
gtk_widget_set_sensitive (wid, !setup_prefs.hex_text_indent);
|
||||||
|
gtk_widget_set_sensitive (cbox, !setup_prefs.hex_text_indent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, ti
|
|||||||
memcpy (new_text, stamp, stamp_size);
|
memcpy (new_text, stamp, stamp_size);
|
||||||
g_free (stamp);
|
g_free (stamp);
|
||||||
memcpy (new_text + stamp_size, text, len);
|
memcpy (new_text + stamp_size, text, len);
|
||||||
gtk_xtext_append (xtbuf, new_text, len + stamp_size, timet);
|
gtk_xtext_append_wrap (xtbuf, new_text, len + stamp_size, timet, stamp_size);
|
||||||
g_free (new_text);
|
g_free (new_text);
|
||||||
} else
|
} else
|
||||||
gtk_xtext_append (xtbuf, text, len, timet);
|
gtk_xtext_append (xtbuf, text, len, timet);
|
||||||
|
|||||||
+46
-4
@@ -81,6 +81,7 @@ struct textentry
|
|||||||
gint16 mark_end;
|
gint16 mark_end;
|
||||||
int indent;
|
int indent;
|
||||||
gint16 left_len;
|
gint16 left_len;
|
||||||
|
gint16 stamp_len; /* bytes of str taken by the timestamp (no-indent mode) */
|
||||||
GSList *slp;
|
GSList *slp;
|
||||||
GArray *sublines; /* byte offsets at the end of each screen row */
|
GArray *sublines; /* byte offsets at the end of each screen row */
|
||||||
int subline_count;
|
int subline_count;
|
||||||
@@ -296,6 +297,7 @@ static void gtk_xtext_get_property (GObject *object, guint prop_id,
|
|||||||
static void gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *);
|
static void gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *);
|
||||||
static void gtk_xtext_recalc_widths (xtext_buffer *buf, int);
|
static void gtk_xtext_recalc_widths (xtext_buffer *buf, int);
|
||||||
static void gtk_xtext_fix_indent (xtext_buffer *buf);
|
static void gtk_xtext_fix_indent (xtext_buffer *buf);
|
||||||
|
static int gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len);
|
||||||
static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line);
|
static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line);
|
||||||
/* For use by gtk_xtext_strip_color() and its callers -- */
|
/* For use by gtk_xtext_strip_color() and its callers -- */
|
||||||
struct offlen_s {
|
struct offlen_s {
|
||||||
@@ -1322,6 +1324,36 @@ gtk_xtext_selection_clear (xtext_buffer *buf)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* x position that the lines after the first of 'ent' start at */
|
||||||
|
|
||||||
|
static int
|
||||||
|
gtk_xtext_cont_indent (xtext_buffer *buf, textentry *ent)
|
||||||
|
{
|
||||||
|
int w, off;
|
||||||
|
|
||||||
|
if (ent->left_len != -1 || prefs.hex_text_wrap_align < 1)
|
||||||
|
return buf->indent;
|
||||||
|
|
||||||
|
/* 1 = under the prefix (nick), 2 = under the message */
|
||||||
|
off = ent->stamp_len;
|
||||||
|
if (prefs.hex_text_wrap_align >= 2)
|
||||||
|
{
|
||||||
|
/* the nick (or "*") is the first word after the timestamp */
|
||||||
|
while (off < ent->str_len && ent->str[off] != ' ')
|
||||||
|
off++;
|
||||||
|
while (off < ent->str_len && ent->str[off] == ' ')
|
||||||
|
off++;
|
||||||
|
}
|
||||||
|
if (off <= 0 || off >= ent->str_len)
|
||||||
|
return buf->indent;
|
||||||
|
|
||||||
|
w = ent->indent + gtk_xtext_text_width (buf->xtext, ent->str, off);
|
||||||
|
/* leave room for the text, or just wrap normally */
|
||||||
|
if (w > (buf->window_width - MARGIN) / 2)
|
||||||
|
return buf->indent;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
find_x (GtkXText *xtext, textentry *ent, int x, int subline, int indent)
|
find_x (GtkXText *xtext, textentry *ent, int x, int subline, int indent)
|
||||||
{
|
{
|
||||||
@@ -1414,7 +1446,7 @@ gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline,
|
|||||||
if (subline < 1)
|
if (subline < 1)
|
||||||
indent = ent->indent;
|
indent = ent->indent;
|
||||||
else
|
else
|
||||||
indent = xtext->buffer->indent;
|
indent = gtk_xtext_cont_indent (xtext->buffer, ent);
|
||||||
|
|
||||||
if (line > gtk_adjustment_get_page_size (xtext->adj) || line < 0)
|
if (line > gtk_adjustment_get_page_size (xtext->adj) || line < 0)
|
||||||
{
|
{
|
||||||
@@ -4035,7 +4067,7 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
|
|||||||
taken--;
|
taken--;
|
||||||
}
|
}
|
||||||
|
|
||||||
indent = xtext->buffer->indent;
|
indent = gtk_xtext_cont_indent (xtext->buffer, ent);
|
||||||
line++;
|
line++;
|
||||||
taken++;
|
taken++;
|
||||||
str += len;
|
str += len;
|
||||||
@@ -4296,7 +4328,7 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry *ent)
|
|||||||
str += len;
|
str += len;
|
||||||
end = str - ent->str;
|
end = str - ent->str;
|
||||||
g_array_append_val (ent->sublines, end);
|
g_array_append_val (ent->sublines, end);
|
||||||
indent = buf->indent;
|
indent = gtk_xtext_cont_indent (buf, ent);
|
||||||
}
|
}
|
||||||
while (str < ent->str + ent->str_len);
|
while (str < ent->str + ent->str_len);
|
||||||
}
|
}
|
||||||
@@ -5376,6 +5408,7 @@ gtk_xtext_append_indent (xtext_buffer *buf,
|
|||||||
prefix_width = gtk_xtext_text_width (buf->xtext, str, left_len + 1);
|
prefix_width = gtk_xtext_text_width (buf->xtext, str, left_len + 1);
|
||||||
|
|
||||||
ent->left_len = left_len;
|
ent->left_len = left_len;
|
||||||
|
ent->stamp_len = 0;
|
||||||
ent->str = str;
|
ent->str = str;
|
||||||
ent->str_len = left_len + 1 + right_len;
|
ent->str_len = left_len + 1 + right_len;
|
||||||
ent->indent = buf->indent - prefix_width;
|
ent->indent = buf->indent - prefix_width;
|
||||||
@@ -5421,6 +5454,14 @@ gtk_xtext_append_indent (xtext_buffer *buf,
|
|||||||
|
|
||||||
void
|
void
|
||||||
gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp)
|
gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp)
|
||||||
|
{
|
||||||
|
gtk_xtext_append_wrap (buf, text, len, stamp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stamp_len: how many bytes at the start of text are the timestamp */
|
||||||
|
|
||||||
|
void
|
||||||
|
gtk_xtext_append_wrap (xtext_buffer *buf, unsigned char *text, int len, time_t stamp, int stamp_len)
|
||||||
{
|
{
|
||||||
textentry *ent;
|
textentry *ent;
|
||||||
|
|
||||||
@@ -5438,6 +5479,7 @@ gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp)
|
|||||||
memcpy (ent->str, text, len);
|
memcpy (ent->str, text, len);
|
||||||
ent->indent = 0;
|
ent->indent = 0;
|
||||||
ent->left_len = -1;
|
ent->left_len = -1;
|
||||||
|
ent->stamp_len = (stamp_len > 0 && stamp_len < ent->str_len) ? stamp_len : 0;
|
||||||
|
|
||||||
gtk_xtext_append_entry (buf, ent, stamp);
|
gtk_xtext_append_entry (buf, ent, stamp);
|
||||||
}
|
}
|
||||||
@@ -5474,7 +5516,7 @@ gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gtk_xtext_append (out, ent->str, ent->str_len, 0);
|
gtk_xtext_append_wrap (out, ent->str, ent->str_len, 0, ent->stamp_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out->text_last)
|
if (out->text_last)
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ struct _GtkXTextClass
|
|||||||
|
|
||||||
GtkWidget *gtk_xtext_new (const XTextColor *palette, int separator);
|
GtkWidget *gtk_xtext_new (const XTextColor *palette, int separator);
|
||||||
void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp);
|
void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp);
|
||||||
|
void gtk_xtext_append_wrap (xtext_buffer *buf, unsigned char *text, int len, time_t stamp, int stamp_len);
|
||||||
void gtk_xtext_append_indent (xtext_buffer *buf,
|
void gtk_xtext_append_indent (xtext_buffer *buf,
|
||||||
unsigned char *left_text, int left_len,
|
unsigned char *left_text, int left_len,
|
||||||
unsigned char *right_text, int right_len,
|
unsigned char *right_text, int right_len,
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||||
|
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap uap3 uap10 rescap">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Identity values are filled by build-msix.ps1. For Microsoft Store
|
||||||
|
submissions they must exactly match Product management > Product identity
|
||||||
|
in Partner Center.
|
||||||
|
-->
|
||||||
|
<Identity
|
||||||
|
Name="@IDENTITY_NAME@"
|
||||||
|
Publisher="@PUBLISHER@"
|
||||||
|
Version="@PACKAGE_VERSION@"
|
||||||
|
ProcessorArchitecture="x64" />
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>ZoiteChat</DisplayName>
|
||||||
|
<PublisherDisplayName>@PUBLISHER_DISPLAY_NAME@</PublisherDisplayName>
|
||||||
|
<Description>ZoiteChat IRC client</Description>
|
||||||
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="en-us" />
|
||||||
|
</Resources>
|
||||||
|
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily
|
||||||
|
Name="Windows.Desktop"
|
||||||
|
MinVersion="10.0.19041.0"
|
||||||
|
MaxVersionTested="10.0.26100.0" />
|
||||||
|
<PackageDependency
|
||||||
|
Name="Microsoft.VCLibs.140.00.UWPDesktop"
|
||||||
|
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||||
|
MinVersion="14.0.30704.0" />
|
||||||
|
</Dependencies>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application
|
||||||
|
Id="ZoiteChat"
|
||||||
|
Executable="ZoiteChat\zoitechat.exe"
|
||||||
|
uap10:RuntimeBehavior="packagedClassicApp"
|
||||||
|
uap10:TrustLevel="mediumIL">
|
||||||
|
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="ZoiteChat"
|
||||||
|
Description="ZoiteChat IRC client"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\Square44x44Logo.png" />
|
||||||
|
|
||||||
|
<Extensions>
|
||||||
|
<uap3:Extension Category="windows.protocol">
|
||||||
|
<uap3:Protocol
|
||||||
|
Name="irc"
|
||||||
|
Parameters="--url="%1"" />
|
||||||
|
</uap3:Extension>
|
||||||
|
|
||||||
|
<uap3:Extension Category="windows.fileTypeAssociation">
|
||||||
|
<uap3:FileTypeAssociation
|
||||||
|
Name="zoitechattheme"
|
||||||
|
Parameters=""%1"">
|
||||||
|
<uap:DisplayName>ZoiteChat Theme</uap:DisplayName>
|
||||||
|
<uap:SupportedFileTypes>
|
||||||
|
<uap:FileType>.zct</uap:FileType>
|
||||||
|
<uap:FileType>.hct</uap:FileType>
|
||||||
|
</uap:SupportedFileTypes>
|
||||||
|
</uap3:FileTypeAssociation>
|
||||||
|
</uap3:Extension>
|
||||||
|
</Extensions>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
</Package>
|
||||||
@@ -0,0 +1,392 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $SourceDir,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $OutputPath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidatePattern('^\d+\.\d+\.\d+\.0$')]
|
||||||
|
[string] $PackageVersion,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $IdentityName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $Publisher,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $PublisherDisplayName,
|
||||||
|
|
||||||
|
[switch] $SelfSign
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
|
||||||
|
function Find-WindowsSdkTool {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $ToolName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
|
||||||
|
if ($command) {
|
||||||
|
return $command.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
$kitsRoot = Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin'
|
||||||
|
if (-not (Test-Path $kitsRoot)) {
|
||||||
|
throw "Windows SDK bin directory was not found: $kitsRoot"
|
||||||
|
}
|
||||||
|
|
||||||
|
$tool = Get-ChildItem -Path $kitsRoot -Filter $ToolName -File -Recurse |
|
||||||
|
Where-Object { $_.FullName -match '\\x64\\' } |
|
||||||
|
Sort-Object {
|
||||||
|
$versionDirectory = Split-Path (Split-Path $_.DirectoryName -Parent) -Leaf
|
||||||
|
try {
|
||||||
|
[version] $versionDirectory
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
[version] '0.0'
|
||||||
|
}
|
||||||
|
} -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $tool) {
|
||||||
|
throw "$ToolName was not found in the installed Windows SDK."
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tool.FullName
|
||||||
|
}
|
||||||
|
|
||||||
|
function Escape-Xml {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $Value
|
||||||
|
)
|
||||||
|
|
||||||
|
return [System.Security.SecurityElement]::Escape($Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-MsixAsset {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Drawing.Image] $SourceImage,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int] $Width,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int] $Height,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$bitmap = [System.Drawing.Bitmap]::new($Width, $Height)
|
||||||
|
try {
|
||||||
|
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||||
|
try {
|
||||||
|
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||||
|
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||||||
|
$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
||||||
|
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
|
||||||
|
|
||||||
|
$scale = [Math]::Min($Width / $SourceImage.Width, $Height / $SourceImage.Height)
|
||||||
|
$drawWidth = [Math]::Max(1, [int][Math]::Round($SourceImage.Width * $scale))
|
||||||
|
$drawHeight = [Math]::Max(1, [int][Math]::Round($SourceImage.Height * $scale))
|
||||||
|
$x = [int](($Width - $drawWidth) / 2)
|
||||||
|
$y = [int](($Height - $drawHeight) / 2)
|
||||||
|
|
||||||
|
$graphics.DrawImage($SourceImage, $x, $y, $drawWidth, $drawHeight)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$graphics.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
$bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$bitmap.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = (Resolve-Path $SourceDir).Path
|
||||||
|
if (-not (Test-Path (Join-Path $source 'zoitechat.exe'))) {
|
||||||
|
throw "Staged ZoiteChat executable is missing from $source"
|
||||||
|
}
|
||||||
|
|
||||||
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||||
|
$templatePath = Join-Path $PSScriptRoot 'AppxManifest.xml.in'
|
||||||
|
$iconPath = Join-Path $repoRoot 'data\icons\zoitechat.png'
|
||||||
|
|
||||||
|
if (-not (Test-Path $templatePath)) {
|
||||||
|
throw "MSIX manifest template is missing: $templatePath"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $iconPath)) {
|
||||||
|
throw "ZoiteChat PNG icon is missing: $iconPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([System.IO.Path]::IsPathRooted($OutputPath)) {
|
||||||
|
$output = [System.IO.Path]::GetFullPath($OutputPath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$output = [System.IO.Path]::GetFullPath((Join-Path $PWD $OutputPath))
|
||||||
|
}
|
||||||
|
$outputDirectory = Split-Path $output -Parent
|
||||||
|
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||||
|
|
||||||
|
$stagingRoot = Join-Path $env:RUNNER_TEMP 'zoitechat-msix'
|
||||||
|
$verifyRoot = Join-Path $env:RUNNER_TEMP 'zoitechat-msix-verify'
|
||||||
|
$pfxPath = Join-Path $env:RUNNER_TEMP 'zoitechat-msix-dev.pfx'
|
||||||
|
|
||||||
|
foreach ($path in @($stagingRoot, $verifyRoot)) {
|
||||||
|
if (Test-Path $path) {
|
||||||
|
Remove-Item -Path $path -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Test-Path $pfxPath) {
|
||||||
|
Remove-Item -Path $pfxPath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null
|
||||||
|
$appRoot = Join-Path $stagingRoot 'ZoiteChat'
|
||||||
|
New-Item -ItemType Directory -Path $appRoot -Force | Out-Null
|
||||||
|
Copy-Item -Path (Join-Path $source '*') -Destination $appRoot -Recurse -Force
|
||||||
|
|
||||||
|
# copy.vcxproj stages everything needed by every Inno component. The Store
|
||||||
|
# package keeps the useful bundled plugins and scripting bridges while letting
|
||||||
|
# the Microsoft Store own application updates. The WinSparkle updater is the
|
||||||
|
# only bundled plugin deliberately removed from the Store package.
|
||||||
|
$excludeFiles = @(
|
||||||
|
'portable-mode',
|
||||||
|
'zoitechat-text.exe',
|
||||||
|
'plugins\hcupd.dll',
|
||||||
|
'WinSparkle.dll',
|
||||||
|
'share\download.png'
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($relativePath in $excludeFiles) {
|
||||||
|
$path = Join-Path $appRoot $relativePath
|
||||||
|
if (Test-Path $path) {
|
||||||
|
Remove-Item -Path $path -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($relativePath in @('share\doc\WinSparkle')) {
|
||||||
|
$path = Join-Path $appRoot $relativePath
|
||||||
|
if (Test-Path $path) {
|
||||||
|
Remove-Item -Path $path -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPath in @(
|
||||||
|
'zoitechat.exe',
|
||||||
|
'plugins\hcnotifications-winrt.dll',
|
||||||
|
'plugins\hcchecksum.dll',
|
||||||
|
'plugins\hcexec.dll',
|
||||||
|
'plugins\hcfishlim.dll',
|
||||||
|
'plugins\hcsysinfo.dll',
|
||||||
|
'share\system.png',
|
||||||
|
'share\fonts\zoitechat\NotoColorEmoji.ttf',
|
||||||
|
'share\doc\zoitechat\fonts\OFL.txt',
|
||||||
|
'plugins\hclua.dll',
|
||||||
|
'plugins\hcperl.dll',
|
||||||
|
'plugins\hcpython3.dll',
|
||||||
|
'plugins\hcpython38.dll'
|
||||||
|
)) {
|
||||||
|
if (-not (Test-Path (Join-Path $appRoot $requiredPath))) {
|
||||||
|
throw "MSIX staging is missing required file: $requiredPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||||
|
$match = Get-ChildItem -Path $appRoot -Filter $requiredPattern -File -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $match) {
|
||||||
|
throw "MSIX staging is missing required Python support file: $requiredPattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||||
|
$path = Join-Path (Join-Path $appRoot 'python') $requiredPythonFile
|
||||||
|
if (-not (Test-Path $path)) {
|
||||||
|
throw "MSIX staging is missing required Python bridge file: $requiredPythonFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$assets = Join-Path $stagingRoot 'Assets'
|
||||||
|
New-Item -ItemType Directory -Path $assets -Force | Out-Null
|
||||||
|
|
||||||
|
$image = [System.Drawing.Image]::FromFile($iconPath)
|
||||||
|
try {
|
||||||
|
New-MsixAsset -SourceImage $image -Width 50 -Height 50 `
|
||||||
|
-Path (Join-Path $assets 'StoreLogo.png')
|
||||||
|
New-MsixAsset -SourceImage $image -Width 44 -Height 44 `
|
||||||
|
-Path (Join-Path $assets 'Square44x44Logo.png')
|
||||||
|
New-MsixAsset -SourceImage $image -Width 150 -Height 150 `
|
||||||
|
-Path (Join-Path $assets 'Square150x150Logo.png')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$image.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifest = Get-Content $templatePath -Raw -Encoding UTF8
|
||||||
|
$manifest = $manifest.Replace('@IDENTITY_NAME@', (Escape-Xml $IdentityName))
|
||||||
|
$manifest = $manifest.Replace('@PUBLISHER@', (Escape-Xml $Publisher))
|
||||||
|
$manifest = $manifest.Replace('@PUBLISHER_DISPLAY_NAME@', (Escape-Xml $PublisherDisplayName))
|
||||||
|
$manifest = $manifest.Replace('@PACKAGE_VERSION@', (Escape-Xml $PackageVersion))
|
||||||
|
|
||||||
|
if ($manifest -match '@[A-Z0-9_]+@') {
|
||||||
|
throw "Unexpanded MSIX manifest placeholder: $($Matches[0])"
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifestPath = Join-Path $stagingRoot 'AppxManifest.xml'
|
||||||
|
[System.IO.File]::WriteAllText(
|
||||||
|
$manifestPath,
|
||||||
|
$manifest,
|
||||||
|
[System.Text.UTF8Encoding]::new($false)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Catch malformed XML before MakeAppx so CI reports a useful error.
|
||||||
|
[xml](Get-Content $manifestPath -Raw -Encoding UTF8) | Out-Null
|
||||||
|
|
||||||
|
$makeAppx = Find-WindowsSdkTool -ToolName 'MakeAppx.exe'
|
||||||
|
$signtool = Find-WindowsSdkTool -ToolName 'SignTool.exe'
|
||||||
|
|
||||||
|
if (Test-Path $output) {
|
||||||
|
Remove-Item -Path $output -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
& $makeAppx pack /d $stagingRoot /p $output /o
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "MakeAppx failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
$certificate = $null
|
||||||
|
try {
|
||||||
|
if ($SelfSign) {
|
||||||
|
# Partner Center replaces this development signature with Microsoft's
|
||||||
|
# trusted Store signature. The certificate subject must exactly match
|
||||||
|
# the manifest Publisher for SignTool to accept the package.
|
||||||
|
$certificate = New-SelfSignedCertificate `
|
||||||
|
-Type Custom `
|
||||||
|
-KeyUsage DigitalSignature `
|
||||||
|
-Subject $Publisher `
|
||||||
|
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
||||||
|
-HashAlgorithm SHA256 `
|
||||||
|
-TextExtension @(
|
||||||
|
'2.5.29.37={text}1.3.6.1.5.5.7.3.3',
|
||||||
|
'2.5.29.19={text}'
|
||||||
|
) `
|
||||||
|
-FriendlyName 'ZoiteChat MSIX CI signing certificate'
|
||||||
|
|
||||||
|
if ($certificate.Subject -cne $Publisher) {
|
||||||
|
throw "Generated certificate subject '$($certificate.Subject)' does not exactly match manifest Publisher '$Publisher'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$passwordText = [Guid]::NewGuid().ToString('N')
|
||||||
|
$password = ConvertTo-SecureString -String $passwordText -AsPlainText -Force
|
||||||
|
Export-PfxCertificate -Cert $certificate -FilePath $pfxPath -Password $password | Out-Null
|
||||||
|
|
||||||
|
& $signtool sign /fd SHA256 /f $pfxPath /p $passwordText $output
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "SignTool failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
$signature = Get-AuthenticodeSignature -FilePath $output
|
||||||
|
if (-not $signature.SignerCertificate -or
|
||||||
|
$signature.SignerCertificate.Subject -cne $Publisher) {
|
||||||
|
throw 'MSIX signature does not match the manifest Publisher.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $verifyRoot -Force | Out-Null
|
||||||
|
& $makeAppx unpack /p $output /d $verifyRoot /o
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "MakeAppx validation unpack failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml] $verifiedManifest = Get-Content (Join-Path $verifyRoot 'AppxManifest.xml') -Raw -Encoding UTF8
|
||||||
|
$ns = New-Object System.Xml.XmlNamespaceManager($verifiedManifest.NameTable)
|
||||||
|
$ns.AddNamespace('f', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10')
|
||||||
|
$identity = $verifiedManifest.SelectSingleNode('/f:Package/f:Identity', $ns)
|
||||||
|
if (-not $identity) {
|
||||||
|
throw 'Validated MSIX does not contain a package Identity.'
|
||||||
|
}
|
||||||
|
if ($identity.Name -cne $IdentityName -or
|
||||||
|
$identity.Publisher -cne $Publisher -or
|
||||||
|
$identity.Version -cne $PackageVersion) {
|
||||||
|
throw 'Validated MSIX identity does not match the requested Store identity.'
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPath in @(
|
||||||
|
'ZoiteChat\zoitechat.exe',
|
||||||
|
'ZoiteChat\plugins\hcnotifications-winrt.dll',
|
||||||
|
'ZoiteChat\plugins\hcchecksum.dll',
|
||||||
|
'ZoiteChat\plugins\hcexec.dll',
|
||||||
|
'ZoiteChat\plugins\hcfishlim.dll',
|
||||||
|
'ZoiteChat\plugins\hcsysinfo.dll',
|
||||||
|
'ZoiteChat\share\system.png',
|
||||||
|
'ZoiteChat\share\fonts\zoitechat\NotoColorEmoji.ttf',
|
||||||
|
'ZoiteChat\share\doc\zoitechat\fonts\OFL.txt',
|
||||||
|
'ZoiteChat\plugins\hclua.dll',
|
||||||
|
'ZoiteChat\plugins\hcperl.dll',
|
||||||
|
'ZoiteChat\plugins\hcpython3.dll',
|
||||||
|
'ZoiteChat\plugins\hcpython38.dll'
|
||||||
|
)) {
|
||||||
|
if (-not (Test-Path (Join-Path $verifyRoot $requiredPath))) {
|
||||||
|
throw "Validated MSIX is missing required file: $requiredPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||||
|
$match = Get-ChildItem -Path (Join-Path $verifyRoot 'ZoiteChat') -Filter $requiredPattern -File -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $match) {
|
||||||
|
throw "Validated MSIX is missing required Python support file: $requiredPattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||||
|
$path = Join-Path (Join-Path $verifyRoot 'ZoiteChat\python') $requiredPythonFile
|
||||||
|
if (-not (Test-Path $path)) {
|
||||||
|
throw "Validated MSIX is missing required Python bridge file: $requiredPythonFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($excludedPath in @(
|
||||||
|
'ZoiteChat\portable-mode',
|
||||||
|
'ZoiteChat\zoitechat-text.exe',
|
||||||
|
'ZoiteChat\plugins\hcupd.dll',
|
||||||
|
'ZoiteChat\WinSparkle.dll',
|
||||||
|
'ZoiteChat\share\download.png'
|
||||||
|
)) {
|
||||||
|
if (Test-Path (Join-Path $verifyRoot $excludedPath)) {
|
||||||
|
throw "MSIX incorrectly contains excluded file/directory: $excludedPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ($certificate) {
|
||||||
|
Remove-Item -Path "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if (Test-Path $pfxPath) {
|
||||||
|
Remove-Item -Path $pfxPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
foreach ($path in @($stagingRoot, $verifyRoot)) {
|
||||||
|
if (Test-Path $path) {
|
||||||
|
Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $output)) {
|
||||||
|
throw "Expected MSIX was not created: $output"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Created MSIX: $output"
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||||
|
<!--
|
||||||
|
This MSI intentionally mirrors the staged portion of the normal Inno Setup
|
||||||
|
payload. Optional/custom-only files are excluded below. In particular,
|
||||||
|
portable-mode must never be harvested because copy.vcxproj always stages
|
||||||
|
that marker. Inno-only download-at-install components remain outside the MSI.
|
||||||
|
-->
|
||||||
|
<Package
|
||||||
|
Name="ZoiteChat"
|
||||||
|
Manufacturer="ZoiteChat"
|
||||||
|
Version="$(ProductVersion)"
|
||||||
|
UpgradeCode="{E9F09DF3-12FA-4BDF-A8AA-A6D62A327ED5}"
|
||||||
|
Scope="perMachine"
|
||||||
|
InstallerVersion="500"
|
||||||
|
Compressed="yes">
|
||||||
|
|
||||||
|
<MajorUpgrade
|
||||||
|
AllowSameVersionUpgrades="yes"
|
||||||
|
DowngradeErrorMessage="A newer version of ZoiteChat is already installed." />
|
||||||
|
|
||||||
|
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||||
|
|
||||||
|
<Icon Id="ZoiteChat.ico" SourceFile="$(sys.SOURCEFILEDIR)..\..\data\icons\zoitechat.ico" />
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="ZoiteChat.ico" />
|
||||||
|
<Property Id="ARPHELPLINK" Value="https://github.com/ZoiteChat/zoitechat/issues" />
|
||||||
|
<Property Id="ARPURLINFOABOUT" Value="https://zoitechat.org/" />
|
||||||
|
<Property Id="ARPURLUPDATEINFO" Value="https://zoitechat.org/downloads.html" />
|
||||||
|
<Property Id="ARPNOMODIFY" Value="1" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The Inno installer can bootstrap the VC++ runtime. A plain MSI should not
|
||||||
|
run an EXE prerequisite as an MSI custom action, so require the runtime.
|
||||||
|
Administrative extraction is allowed regardless of prerequisite state.
|
||||||
|
-->
|
||||||
|
<Property Id="VCREDIST_X64_INSTALLED">
|
||||||
|
<RegistrySearch
|
||||||
|
Id="FindVCRedistX64"
|
||||||
|
Root="HKLM"
|
||||||
|
Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"
|
||||||
|
Name="Installed"
|
||||||
|
Type="raw"
|
||||||
|
Bitness="always64" />
|
||||||
|
</Property>
|
||||||
|
<Launch
|
||||||
|
Condition="Installed OR ACTION = "ADMIN" OR VCREDIST_X64_INSTALLED = "#1""
|
||||||
|
Message="Microsoft Visual C++ 2015-2022 x64 Redistributable is required before installing ZoiteChat." />
|
||||||
|
<Launch
|
||||||
|
Condition="Installed OR ACTION = "ADMIN" OR VersionNT64 > 601 OR (VersionNT64 = 601 AND ServicePackLevel >= 1)"
|
||||||
|
Message="ZoiteChat requires 64-bit Windows 7 SP1 or later." />
|
||||||
|
|
||||||
|
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||||
|
<Directory Id="INSTALLFOLDER" Name="ZoiteChat" />
|
||||||
|
</StandardDirectory>
|
||||||
|
|
||||||
|
<StandardDirectory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ApplicationProgramsFolder" Name="ZoiteChat" />
|
||||||
|
</StandardDirectory>
|
||||||
|
|
||||||
|
<Feature Id="MainFeature" Title="ZoiteChat" Level="1">
|
||||||
|
<ComponentGroupRef Id="ApplicationFiles" />
|
||||||
|
<ComponentRef Id="StartMenuShortcuts" />
|
||||||
|
<ComponentRef Id="ProtocolAndThemeAssociations" />
|
||||||
|
</Feature>
|
||||||
|
</Package>
|
||||||
|
|
||||||
|
<Fragment>
|
||||||
|
<ComponentGroup Id="ApplicationFiles">
|
||||||
|
<Files Directory="INSTALLFOLDER" Include="$(SourceDir)\**">
|
||||||
|
<!-- Created by copy.vcxproj for Inno's optional portable task only. -->
|
||||||
|
<Exclude Files="$(SourceDir)\portable-mode" />
|
||||||
|
<!-- Inno custom-only application component. -->
|
||||||
|
<Exclude Files="$(SourceDir)\zoitechat-text.exe" />
|
||||||
|
<!--
|
||||||
|
Keep the bundled utility and scripting plugins, including Python
|
||||||
|
3.14 and 3.8 support, available to non-Inno installations.
|
||||||
|
-->
|
||||||
|
</Files>
|
||||||
|
</ComponentGroup>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<Fragment>
|
||||||
|
<Component Id="StartMenuShortcuts" Directory="ApplicationProgramsFolder" Guid="*">
|
||||||
|
<Shortcut
|
||||||
|
Id="ZoiteChatShortcut"
|
||||||
|
Name="ZoiteChat"
|
||||||
|
Description="ZoiteChat IRC client"
|
||||||
|
Target="[INSTALLFOLDER]zoitechat.exe"
|
||||||
|
WorkingDirectory="INSTALLFOLDER"
|
||||||
|
Icon="ZoiteChat.ico">
|
||||||
|
<ShortcutProperty Key="System.AppUserModel.ID" Value="ZoiteChat.Desktop.Notify" />
|
||||||
|
</Shortcut>
|
||||||
|
|
||||||
|
<Shortcut
|
||||||
|
Id="ZoiteChatSafeModeShortcut"
|
||||||
|
Name="ZoiteChat Safe Mode"
|
||||||
|
Description="Start ZoiteChat without auto-connect or plugins"
|
||||||
|
Target="[INSTALLFOLDER]zoitechat.exe"
|
||||||
|
Arguments="--no-auto --no-plugins"
|
||||||
|
WorkingDirectory="INSTALLFOLDER"
|
||||||
|
Icon="ZoiteChat.ico" />
|
||||||
|
|
||||||
|
<Shortcut
|
||||||
|
Id="ZoiteChatChangelogShortcut"
|
||||||
|
Name="ZoiteChat ChangeLog"
|
||||||
|
Target="[INSTALLFOLDER]changelog.url" />
|
||||||
|
|
||||||
|
<Shortcut
|
||||||
|
Id="ZoiteChatReadmeShortcut"
|
||||||
|
Name="ZoiteChat ReadMe"
|
||||||
|
Target="[INSTALLFOLDER]readme.url" />
|
||||||
|
|
||||||
|
<Shortcut
|
||||||
|
Id="ZoiteChatUninstallShortcut"
|
||||||
|
Name="Uninstall ZoiteChat"
|
||||||
|
Target="[System64Folder]msiexec.exe"
|
||||||
|
Arguments="/x [ProductCode]" />
|
||||||
|
|
||||||
|
<RemoveFolder Id="RemoveApplicationProgramsFolder" On="uninstall" />
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKLM"
|
||||||
|
Key="Software\ZoiteChat\Installer"
|
||||||
|
Name="StartMenuShortcuts"
|
||||||
|
Type="integer"
|
||||||
|
Value="1"
|
||||||
|
KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<Fragment>
|
||||||
|
<Component Id="ProtocolAndThemeAssociations" Directory="INSTALLFOLDER" Guid="*">
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCR"
|
||||||
|
Key="irc"
|
||||||
|
Type="string"
|
||||||
|
Value="URL:IRC Protocol"
|
||||||
|
KeyPath="yes" />
|
||||||
|
<RegistryValue Root="HKCR" Key="irc" Name="URL Protocol" Type="string" Value="" />
|
||||||
|
<RegistryValue Root="HKCR" Key="irc\DefaultIcon" Type="string" Value="[INSTALLFOLDER]zoitechat.exe,0" />
|
||||||
|
<RegistryValue Root="HKCR" Key="irc\shell" Type="string" Value="open" />
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCR"
|
||||||
|
Key="irc\shell\open\command"
|
||||||
|
Type="string"
|
||||||
|
Value=""[INSTALLFOLDER]zoitechat.exe" --url="%1"" />
|
||||||
|
|
||||||
|
<RegistryValue Root="HKCR" Key=".zct" Type="string" Value="ZoiteChat.Theme" />
|
||||||
|
<RegistryValue Root="HKCR" Key=".hct" Type="string" Value="ZoiteChat.Theme" />
|
||||||
|
<RegistryValue Root="HKCR" Key="ZoiteChat.Theme" Type="string" Value="ZoiteChat Theme" />
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCR"
|
||||||
|
Key="ZoiteChat.Theme\DefaultIcon"
|
||||||
|
Type="string"
|
||||||
|
Value="[INSTALLFOLDER]zoitechat.exe,0" />
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCR"
|
||||||
|
Key="ZoiteChat.Theme\shell\open\command"
|
||||||
|
Type="string"
|
||||||
|
Value=""[INSTALLFOLDER]zoitechat.exe" "%1"" />
|
||||||
|
</Component>
|
||||||
|
</Fragment>
|
||||||
|
</Wix>
|
||||||
Reference in New Issue
Block a user