diff --git a/.forgejo/workflows/windows_release.yml b/.forgejo/workflows/windows_release.yml index 8ab39d6..525c04d 100644 --- a/.forgejo/workflows/windows_release.yml +++ b/.forgejo/workflows/windows_release.yml @@ -1,8 +1,7 @@ name: Windows Release -# Forgejo-orchestrated Windows portable zip. -# Tower `desktop-release` triggers the private GitHub builder (MSVC), then -# verifies the public Forgejo release tag `windows-continuous`. +# Continuous Windows portable build on every push to main. +# Needs self-hosted runner label `windows-release` (MSVC 2022). # See docs/windows-runner.md on: @@ -19,18 +18,30 @@ concurrency: jobs: windows-x86_64: - runs-on: desktop-release - timeout-minutes: 360 + runs-on: windows-release + timeout-minutes: 240 steps: - - name: Resolve expected asset + - name: Checkout + shell: bash + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + rm -rf .git + git init . + origin_url="${GITHUB_SERVER_URL#https://}" + origin_url="${origin_url#http://}" + git remote add origin "https://oauth2:${FORGEJO_TOKEN}@${origin_url}/${GITHUB_REPOSITORY}.git" + git -c http.sslVerify=false fetch --depth=1 origin "${GITHUB_SHA}" + git checkout --force FETCH_HEAD + git submodule update --init --recursive + + - name: Resolve version id: ver shell: bash run: | set -euo pipefail - # Shallow metadata from the triggering commit (no full Windows checkout). - ver="$(curl -fsSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/contents/src/VersionConstants.hpp?ref=${GITHUB_SHA}" \ - | python3 -c "import sys,json,base64,re; d=json.load(sys.stdin); t=base64.b64decode(d['content']).decode(); print(re.search(r'CURRENT_VERSION_STRING\s*=\s*\"([^\"]+)\"', t).group(1))")" + ver="$(grep -E 'CURRENT_VERSION_STRING' src/VersionConstants.hpp | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)" asset_ver="${ver}+${short_sha}" zip_name="infinipaint-${asset_ver}-win-x86_64-portable.zip" @@ -39,87 +50,129 @@ jobs: echo "tag=windows-continuous" echo "asset_ver=${asset_ver}" echo "zip_name=${zip_name}" - echo "short_sha=${short_sha}" } >> "$GITHUB_OUTPUT" - echo "Expect Forgejo asset: ${zip_name}" - - name: Dispatch or reuse GitHub Windows build - id: gh - shell: bash - env: - GH_BUILDER_TOKEN: ${{ secrets.GH_BUILDER_TOKEN }} - WANT_SHA: ${{ github.sha }} + - name: Ensure Conan + shell: pwsh run: | - set -euo pipefail - if [ -z "${GH_BUILDER_TOKEN:-}" ]; then - echo "GH_BUILDER_TOKEN secret missing on Forgejo repo" >&2 - exit 1 - fi - api="https://api.github.com/repos/littlecodedragon/infinipaint" - auth=(-H "Authorization: Bearer ${GH_BUILDER_TOKEN}" -H "Accept: application/vnd.github+json") - gh_sha=$(curl -fsSL "${auth[@]}" "${api}/commits/main" | python3 -c "import sys,json; print(json.load(sys.stdin)['sha'])") - if [ "$gh_sha" != "$WANT_SHA" ]; then - echo "WARN: GitHub main=${gh_sha:0:7} != Forgejo ${WANT_SHA:0:7} — keep remotes synced (push both)." >&2 - fi - runs=$(curl -fsSL "${auth[@]}" "${api}/actions/workflows/windows_release.yml/runs?branch=main&per_page=15") - run_id=$(echo "$runs" | python3 -c " - import sys, json, os - want = os.environ['WANT_SHA'] - runs = json.load(sys.stdin).get('workflow_runs', []) - # Prefer live or successful run for this SHA. - for r in runs: - if r.get('head_sha') != want: - continue - st, conc = r.get('status'), r.get('conclusion') - if st in ('queued', 'in_progress') or (st == 'completed' and conc == 'success'): - print(r['id']) - break - ") - if [ -z "${run_id}" ]; then - echo "No usable GitHub run for ${WANT_SHA:0:7} — dispatching workflow_dispatch" - code=$(curl -sS -o /tmp/gh-dispatch.json -w '%{http_code}' -X POST "${auth[@]}" \ - -H "Content-Type: application/json" \ - "${api}/actions/workflows/windows_release.yml/dispatches" \ - -d '{"ref":"main"}') - [ "$code" = "204" ] || { cat /tmp/gh-dispatch.json; exit 1; } - sleep 8 - runs=$(curl -fsSL "${auth[@]}" "${api}/actions/workflows/windows_release.yml/runs?branch=main&per_page=5") - run_id=$(echo "$runs" | python3 -c "import sys,json; print(json.load(sys.stdin)['workflow_runs'][0]['id'])") - fi - echo "run_id=${run_id}" >> "$GITHUB_OUTPUT" - echo "Tracking GitHub run ${run_id}" + $ErrorActionPreference = "Stop" + if (-not (Get-Command conan -ErrorAction SilentlyContinue)) { + python -m pip install --user "conan>=2,<3" + Get-ChildItem "$env:USERPROFILE\AppData\Roaming\Python" -Recurse -Filter conan.exe -ErrorAction SilentlyContinue | + ForEach-Object { $env:Path = "$($_.DirectoryName);$env:Path" } + } + conan --version - - name: Wait for GitHub build + Forgejo public release - shell: bash + - name: Build Windows x86_64 + shell: cmd + run: | + call windowsinstall\conan_init_x86_64.bat + if errorlevel 1 exit /b 1 + call windowsinstall\build_x86_64.bat + if errorlevel 1 exit /b 1 + + - name: Package portable zip + id: pkg + shell: pwsh env: - GH_BUILDER_TOKEN: ${{ secrets.GH_BUILDER_TOKEN }} - FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUN_ID: ${{ steps.gh.outputs.run_id }} ZIP_NAME: ${{ steps.ver.outputs.zip_name }} + run: | + $ErrorActionPreference = "Stop" + $exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe | + Where-Object { $_.FullName -match 'Release' } | + Select-Object -First 1 + if (-not $exe) { throw "infinipaint.exe not found under Release" } + $outDir = Join-Path (Get-Location) "dist\portable" + Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Force -Path $outDir | Out-Null + Copy-Item $exe.FullName (Join-Path $outDir "infinipaint.exe") + $dllDir = Join-Path (Get-Location) "windowsinstall\dlls-x86_64" + if (Test-Path $dllDir) { + Copy-Item (Join-Path $dllDir "*.dll") $outDir -ErrorAction SilentlyContinue + } + $dataSrc = Join-Path (Get-Location) "assets\data" + if (-not (Test-Path $dataSrc)) { $dataSrc = Join-Path (Get-Location) "data" } + Copy-Item -Recurse $dataSrc (Join-Path $outDir "data") + New-Item -ItemType Directory -Force -Path "dist" | Out-Null + $zipPath = Join-Path (Get-Location) "dist\$env:ZIP_NAME" + if (Test-Path $zipPath) { Remove-Item $zipPath } + Compress-Archive -Path (Join-Path $outDir "*") -DestinationPath $zipPath + "zip_path=$zipPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + Get-Item $zipPath | Format-List FullName, Length + + - name: Publish Forgejo release + shell: bash + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION_TAG: ${{ steps.ver.outputs.tag }} ASSET_VER: ${{ steps.ver.outputs.asset_ver }} - SHORT_SHA: ${{ steps.ver.outputs.short_sha }} + VERSION: ${{ steps.ver.outputs.version }} + ZIP_NAME: ${{ steps.ver.outputs.zip_name }} + FORGEJO_CURL_INSECURE: "true" run: | set -euo pipefail - api="https://api.github.com/repos/littlecodedragon/infinipaint" - auth=(-H "Authorization: Bearer ${GH_BUILDER_TOKEN}" -H "Accept: application/vnd.github+json") - fj="https://forgejo.fsociety00.cc/api/v1/repos/copilot/infinipaint/releases/tags/windows-continuous" - for i in $(seq 1 240); do - g=$(curl -fsSL "${auth[@]}" "${api}/actions/runs/${RUN_ID}") - status=$(echo "$g" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('status'), d.get('conclusion') or '-')") - rel=$(curl -fsSL -H "Authorization: token ${FORGEJO_TOKEN}" "$fj" || echo '{}') - rname=$(echo "$rel" | python3 -c "import sys,json;print(json.load(sys.stdin).get('name',''))" 2>/dev/null || true) - assets=$(echo "$rel" | python3 -c "import sys,json;print([a.get('name') for a in json.load(sys.stdin).get('assets',[])])" 2>/dev/null || true) - echo "$(date -u +%H:%M:%S) github=${status} forgejo=${rname} assets=${assets}" - if echo "$assets" | grep -Fq "${ZIP_NAME}" || echo "$rname" | grep -Fq "${SHORT_SHA}"; then - echo "Public Forgejo release is up to date." - exit 0 - fi - if echo "$status" | grep -q '^completed failure\|^completed cancelled\|^completed timed_out'; then - echo "GitHub Windows build failed: ${status}" >&2 - echo "$g" | python3 -c "import sys,json;print(json.load(sys.stdin).get('html_url',''))" - exit 1 - fi - sleep 60 - done - echo "Timed out waiting for Forgejo asset ${ZIP_NAME}" >&2 - exit 1 + zip_path="dist/${ZIP_NAME}" + test -f "$zip_path" + command -v jq >/dev/null + command -v curl >/dev/null + + curl_tls=() + if [ "${FORGEJO_CURL_INSECURE}" = "true" ]; then + curl_tls+=(--insecure) + fi + + api_base="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" + body=$(printf '%s\n' \ + "Continuous Windows build from \`main\`." \ + "" \ + "- Version: **${VERSION}**" \ + "- Commit: \`${GITHUB_SHA}\`" \ + "- Asset: \`${ZIP_NAME}\`" \ + "" \ + "Updated on every push to \`main\`. Requires runner label \`windows-release\` (see docs/windows-runner.md).") + + payload=$(jq -n \ + --arg tag "$VERSION_TAG" \ + --arg name "Windows continuous (${ASSET_VER})" \ + --arg body "$body" \ + '{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:true}') + + tag_enc=$(jq -nr --arg t "$VERSION_TAG" '$t|@uri') + resp_file=$(mktemp) + status=$(curl "${curl_tls[@]}" -sS -o "$resp_file" -w '%{http_code}' \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + "${api_base}/tags/${tag_enc}") + + release_id="" + if [ "$status" = "200" ]; then + release_id=$(jq -r '.id' < "$resp_file") + curl "${curl_tls[@]}" -sS -X PATCH \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload" \ + "${api_base}/${release_id}" >/dev/null + jq -r --arg n "$ZIP_NAME" '.assets[]? | select(.name==$n) | .id' < "$resp_file" | while read -r aid; do + [ -n "$aid" ] || continue + curl "${curl_tls[@]}" -sS -X DELETE \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + "${api_base}/${release_id}/assets/${aid}" >/dev/null || true + done + elif [ "$status" = "404" ]; then + status=$(curl "${curl_tls[@]}" -sS -o "$resp_file" -w '%{http_code}' -X POST \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload" \ + "$api_base") + [ "$status" = "200" ] || [ "$status" = "201" ] || { cat "$resp_file"; exit 1; } + release_id=$(jq -r '.id' < "$resp_file") + else + cat "$resp_file"; exit 1 + fi + + name_enc=$(jq -nr --arg n "$ZIP_NAME" '$n|@uri') + curl "${curl_tls[@]}" -sS -X POST \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"${zip_path}" \ + "${api_base}/${release_id}/assets?name=${name_enc}" \ + | jq -r '.browser_download_url // .name // .' diff --git a/.github/workflows/windows_release.yml b/.github/workflows/windows_release.yml index eff731a..b35c397 100644 --- a/.github/workflows/windows_release.yml +++ b/.github/workflows/windows_release.yml @@ -105,7 +105,6 @@ jobs: shell: pwsh env: ZIP_NAME: ${{ steps.ver.outputs.zip_name }} - ASSET_VER: ${{ steps.ver.outputs.asset_ver }} run: | $ErrorActionPreference = "Stop" $exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe | @@ -116,8 +115,6 @@ jobs: Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $outDir | Out-Null Copy-Item $exe.FullName (Join-Path $outDir "infinipaint.exe") - # Identity file for humans / debugging (app also embeds SHA at compile time). - Set-Content -Path (Join-Path $outDir "version.txt") -Value $env:ASSET_VER -Encoding ascii $dllDir = Join-Path (Get-Location) "windowsinstall\dlls-x86_64" if (Test-Path $dllDir) { Copy-Item (Join-Path $dllDir "*.dll") $outDir -ErrorAction SilentlyContinue diff --git a/CMakeLists.txt b/CMakeLists.txt index d01183f..0a79194 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ set(sources "src/main.cpp" "src/DrawData.cpp" "src/FontData.cpp" "src/ClientData.cpp" - "src/CollabPermissions.cpp" "src/GridManager.cpp" "src/WorldGrid.cpp" "src/TimePoint.cpp" @@ -54,7 +53,6 @@ set(sources "src/main.cpp" "src/WorldScreenshot.cpp" "src/FileHelpers.cpp" "src/VersionConstants.cpp" - "src/WindowsPortableUpdater.cpp" "src/ScaleUpCanvas.cpp" "src/WorldUndoManager.cpp" "src/GUIHolder.cpp" @@ -201,30 +199,6 @@ else() add_executable(main ${sources}) endif() -# Short git SHA for continuous-release identity (Windows auto-update, About). -set(INFINIPAINT_GIT_SHA "unknown") -find_package(Git QUIET) -if(GIT_FOUND) - execute_process( - COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - OUTPUT_VARIABLE INFINIPAINT_GIT_SHA - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) -endif() -if(NOT INFINIPAINT_GIT_SHA) - set(INFINIPAINT_GIT_SHA "unknown") -endif() -file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated") -configure_file( - "${CMAKE_SOURCE_DIR}/src/BuildCommit.hpp.in" - "${CMAKE_BINARY_DIR}/generated/BuildCommit.hpp" - @ONLY -) -target_include_directories(main PRIVATE "${CMAKE_BINARY_DIR}/generated") -message(STATUS "InfiniPaint build commit: ${INFINIPAINT_GIT_SHA}") - # Get libraries if(NO_CONAN_BUILD) @@ -407,8 +381,7 @@ else() endif() if((NOT EMSCRIPTEN) AND WIN32) - target_link_libraries(main dwrite shell32) - # shell32: ShellExecuteA for portable auto-update helper script + target_link_libraries(main dwrite) # Clip library for retrieving images from windows clipboard set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples") set(CLIP_TESTS OFF CACHE BOOL "Compile clip tests") diff --git a/FORK.md b/FORK.md index 9010ebb..efa2328 100644 --- a/FORK.md +++ b/FORK.md @@ -4,17 +4,16 @@ Upstream: [ErrorAtLine0/infinipaint](https://github.com/ErrorAtLine0/infinipaint **Public forge:** https://forgejo.fsociety00.cc/copilot/infinipaint GitHub (`littlecodedragon/infinipaint`) is **private builder only** — not a public fork mirror. Source of truth stays on Forgejo. -Save format: `INFPNT000009` / version `0.9.0-fork` (not network-compatible with stock 0.6.x peers). +Save format: `INFPNT000008` / version `0.8.0-fork` (not network-compatible with stock 0.6.x peers). ## CI | Workflow | Trigger | Runner | Output | |----------|---------|--------|--------| -| `.forgejo/workflows/windows_release.yml` | push `main` on Forgejo | Tower **`desktop-release`** | dispatches/waits private GitHub MSVC build, verifies Forgejo tag `windows-continuous` | -| `.github/workflows/windows_release.yml` | private GitHub mirror | GitHub-hosted **`windows-latest`** | compiles zip + uploads public Forgejo `windows-continuous` | +| `.github/workflows/windows_release.yml` | push `main` to private GitHub mirror | GitHub-hosted **`windows-latest`** | uploads public Forgejo tag `windows-continuous` | +| `.forgejo/workflows/windows_release.yml` | every push to `main` | Forgejo self-hosted `windows-release` (optional) | same tag on Forgejo | **Friends (Windows):** https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous — zip entpacken, `infinipaint.exe` starten. -Portable Windows builds check that Forgejo tag on startup (if “Check for updates” is on) and can **download + install** the newer zip in place. Details: [docs/windows-runner.md](docs/windows-runner.md). ## Bring Everyone Here (host) @@ -34,18 +33,4 @@ Not automatic on import. Choose **Paste Image + Freistellen** from the canvas ri ## Fog of War / layer lock (host) -In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** is a quick toggle that sets layer-default Draw/Move/Delete to Deny; fog layers are always locked for players (move/edit/erase rejected on the host). Players also **cannot hide or fade** a fog layer (eye / alpha); only the host can toggle fog visibility, and the host rejects client DisplayData that would strip FoW. FoW visibility is still global (not per-person). - -## Collab permissions (host) - -Rights: **Draw**, **Move**, **Delete**, **Hide**, **Layers** (create/reorder/delete layers). Each right is Allow / Deny / Inherit. - -Resolution (first concrete wins): Person×Layer override → Layer defaults → Person defaults → World default (Inherit at world = Allow). - -Host UI: - -- Player list → **Session Defaults…** (world / new-player / new-layer templates) -- Player list → **Rights** per connected player (person defaults; copied from new-player template on join) -- Layer panel (host): layer defaults + per-player overrides for the selected layer - -Clients cannot escalate their own ACL; host DisplayData/ClientData updates are sanitized. +In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** blocks client edits on a normal layer; fog layers are always locked for players (move/edit/erase rejected on the host). diff --git a/docs/windows-runner.md b/docs/windows-runner.md index fde81cd..d071a10 100644 --- a/docs/windows-runner.md +++ b/docs/windows-runner.md @@ -1,15 +1,10 @@ -# Windows continuous release (Forgejo → private GitHub builder) +# Windows continuous release (GitHub Actions as private builder) Native InfiniPaint Windows builds need **MSVC** (see `docs/BUILDING.md` + `windowsinstall/`). There is no Linux cross-build path. -Policy: - -- **Forgejo** (`copilot/infinipaint`) is the **public** fork and download site. -- **GitHub** (`littlecodedragon/infinipaint`) is a **private** builder mirror only - (`windows-latest` MSVC). Must stay private. -- Tower **`desktop-release`** runs the Forgejo workflow: it dispatches/waits on - the private GitHub build, then checks the public Forgejo release. +Policy: **Forgejo is the public fork**; GitHub is only a **private** mirror used +to run `windows-latest` CI. Friends download the zip from Forgejo — not GitHub. ## Public download (friends) @@ -17,39 +12,36 @@ https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuou Portable zip → extract → run `infinipaint.exe`. -### Auto-update (portable) +## Workflow -Windows builds embed the short git SHA at configure time. On startup they poll the -public Forgejo API for tag `windows-continuous`. If the release commit differs, -a dialog offers **Download and install update** (downloads the zip, extracts over -the install folder via a helper script after quit, then restarts). Toggle via -Settings → check for updates. +File: `.github/workflows/windows_release.yml` -## Workflows +| | | +|---|---| +| Trigger | every push to `main` on the private GitHub mirror, plus `workflow_dispatch` | +| Runner | `windows-latest` (GitHub-hosted, MSVC ~195 / VS 18) | +| Build | Conan profile `win-ci` + **Ninja** (avoids CMake<4 vs `Visual Studio 18 2026`) | +| Output | public Forgejo prerelease **`windows-continuous`** (+ private GitHub copy) | -| File | Runner | Role | -|------|--------|------| -| `.forgejo/workflows/windows_release.yml` | `desktop-release` (Tower) | Orchestrate + verify Forgejo release | -| `.github/workflows/windows_release.yml` | `windows-latest` (private) | Compile + upload Forgejo asset | - -Forgejo repo secret: **`GH_BUILDER_TOKEN`** (GitHub PAT with `actions:write` / -`contents:read` on the private mirror). - -GitHub Actions secret: **`FORGEJO_TOKEN`** (Forgejo PAT with release write on +Required GitHub Actions secret: `FORGEJO_TOKEN` (Forgejo PAT with release write on `copilot/infinipaint`). -## Tray +Local Tower Windows can keep using `windowsinstall/conan_init_x86_64.bat` +(`conan/profiles/win-x86_64`, MSVC 194). CI generates a `win-ci` profile for +whatever MSVC `windows-latest` ships. -Tower CI job tray (`ci-runner-job-tray.py`) tracks: - -- `Forgejo desktop-release` — host unit (orchestrator + APK jobs) -- `Forgejo InfiniPaint Windows` — Forgejo Actions run for this workflow - -## Sync remotes +## GitHub remote (private builder) ```bash -git push origin main # Forgejo (public) -git push github main # private builder +git remote add github https://github.com/littlecodedragon/infinipaint.git +git push -u github main ``` -Keep GitHub **private**. +Keep the repo **private** (Settings → Change visibility, or +`gh repo edit littlecodedragon/infinipaint --visibility private`). + +## Forgejo note + +`.forgejo/workflows/windows_release.yml` still targets a self-hosted +`windows-release` runner (optional). Primary public zips come from the GitHub +builder → Forgejo release upload above. diff --git a/linuxinstall/install-local.sh b/linuxinstall/install-local.sh old mode 100755 new mode 100644 diff --git a/src/BuildCommit.hpp.in b/src/BuildCommit.hpp.in deleted file mode 100644 index 7ae8228..0000000 --- a/src/BuildCommit.hpp.in +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -// Generated at configure time from git HEAD (short sha). -#define INFINIPAINT_BUILD_COMMIT "@INFINIPAINT_GIT_SHA@" diff --git a/src/CanvasComponents/CanvasComponentAllocator.cpp b/src/CanvasComponents/CanvasComponentAllocator.cpp index 89dcaac..059b233 100644 --- a/src/CanvasComponents/CanvasComponentAllocator.cpp +++ b/src/CanvasComponents/CanvasComponentAllocator.cpp @@ -45,10 +45,10 @@ void CanvasComponentAllocator::register_class(World& world) { .writeUpdate = [](const CanvasComponentAllocator& o, cereal::PortableBinaryOutputArchive& a) { a(*o.comp); }, - .readUpdate = [&world](CanvasComponentAllocator& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { - // Host rejects client edits without Draw on that layer (still consume the payload). - if(c && o.comp && o.comp->compContainer - && !world.drawProg.layerMan.client_may_modify_component(o.comp->compContainer, CollabRight::Draw)) { + .readUpdate = [](CanvasComponentAllocator& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { + // Host rejects client edits of fog/locked-layer components (still consume the payload). + if(c && o.comp && o.comp->compContainer && o.comp->compContainer->parentLayer + && o.comp->compContainer->parentLayer->is_player_edit_blocked()) { auto junk = std::unique_ptr(CanvasComponent::allocate_comp(o.comp->get_type())); a(*junk); return; diff --git a/src/ClientData.cpp b/src/ClientData.cpp index 8c5ad4b..76442e1 100644 --- a/src/ClientData.cpp +++ b/src/ClientData.cpp @@ -36,8 +36,7 @@ enum class ClientDataCommand : uint8_t { SET_WINDOW_SIZE, SET_CAM_COORDS, SEND_CHAT_MESSAGE, - SET_GRID_SIZE, - SET_PERSON_DEFAULTS + SET_GRID_SIZE }; ClientData::ClientData() { @@ -55,7 +54,6 @@ void ClientData::set_from_init_struct(const InitStruct& initStruct) { cursorColor = initStruct.cursorColor; displayName = initStruct.displayName; gridSize = initStruct.gridSize; - personDefaults = initStruct.personDefaults; } void ClientData::register_class(World& world) { @@ -94,11 +92,6 @@ void ClientData::register_class(World& world) { } break; } - case ClientDataCommand::SET_PERSON_DEFAULTS: - a(o->personDefaults); - world.drawProg.layerMan.ensure_editing_layer_is_editable(); - world.set_to_layout_gui_if_focus(); - break; } }, .writeConstructorFuncServer = default_serialize_write_func, @@ -148,20 +141,6 @@ void ClientData::register_class(World& world) { }); break; } - case ClientDataCommand::SET_PERSON_DEFAULTS: { - // Only the host may change ACLs — junk-consume client attempts. - CollabPermissionSet incoming; - a(incoming); - if(c) - break; - o->personDefaults = incoming; - o.send_server_update_to_all_clients(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive & a) { - a(ClientDataCommand::SET_PERSON_DEFAULTS, o->personDefaults); - }); - world.drawProg.layerMan.ensure_editing_layer_is_editable(); - world.set_to_layout_gui_if_focus(); - break; - } } }, }); @@ -208,15 +187,6 @@ void ClientData::scale_up_step(const NetworkingObjects::NetObjTemporaryPtr& o, const CollabPermissionSet& defaults) { - if(o->personDefaults == defaults) - return; - o->personDefaults = defaults; - o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive & a) { - a(ClientDataCommand::SET_PERSON_DEFAULTS, o->personDefaults); - }); -} - void ClientData::set_display_name(const std::string& newDisplayName) { displayName = newDisplayName; } @@ -249,14 +219,6 @@ const Vector3f& ClientData::get_cursor_color() const { return cursorColor; } -const CollabPermissionSet& ClientData::get_person_defaults() const { - return personDefaults; -} - -CollabPermissionSet& ClientData::person_defaults_mut() { - return personDefaults; -} - void ClientData::draw_cursor(SkCanvas* canvas, const DrawData& drawData) const { if((drawData.cam.c.inverseScale << 10) > camCoords.inverseScale && drawData.clampDrawMinimum < camCoords.inverseScale) { canvas->save(); diff --git a/src/ClientData.hpp b/src/ClientData.hpp index 01aacac..3a4f1da 100644 --- a/src/ClientData.hpp +++ b/src/ClientData.hpp @@ -19,7 +19,6 @@ #pragma once #include #include "CoordSpaceHelper.hpp" -#include "CollabPermissions.hpp" #include #include @@ -33,7 +32,6 @@ class ClientData { Vector3f cursorColor = {1.0f, 1.0f, 1.0f}; std::string displayName; uint32_t gridSize = 0; - CollabPermissionSet personDefaults = CollabPermissionSet::all_inherit(); }; ClientData(); @@ -44,8 +42,6 @@ class ClientData { static void set_camera_coords(const NetworkingObjects::NetObjTemporaryPtr& o, const CoordSpaceHelper& newCoords); static void send_chat_message(const NetworkingObjects::NetObjTemporaryPtr& o, World& world, const std::string& chatMessage); static void scale_up_step(const NetworkingObjects::NetObjTemporaryPtr& o, World& world); - /** Host-only: push person-default ACL to all peers. */ - static void set_person_defaults(const NetworkingObjects::NetObjTemporaryPtr& o, const CollabPermissionSet& defaults); void set_display_name(const std::string& newDisplayName); void set_cursor_color(const Vector3f& newCursorColor); @@ -55,13 +51,11 @@ class ClientData { uint32_t get_grid_size() const; const std::string& get_display_name() const; const Vector3f& get_cursor_color() const; - const CollabPermissionSet& get_person_defaults() const; - CollabPermissionSet& person_defaults_mut(); void draw_cursor(SkCanvas* canvas, const DrawData& drawData) const; template void serialize(Archive& a) { - a(camCoords, windowSize, cursorPos, cursorColor, displayName, gridSize, personDefaults); + a(camCoords, windowSize, cursorPos, cursorColor, displayName, gridSize); } private: void set_from_init_struct(const InitStruct& initStruct); @@ -72,6 +66,5 @@ class ClientData { Vector3f cursorColor; std::string displayName; uint32_t gridSize; - CollabPermissionSet personDefaults; }; diff --git a/src/CollabPermissions.cpp b/src/CollabPermissions.cpp deleted file mode 100644 index b2963c4..0000000 --- a/src/CollabPermissions.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "CollabPermissions.hpp" - -CollabTriState CollabPermissionSet::get(CollabRight right) const { - switch(right) { - case CollabRight::Draw: return draw; - case CollabRight::Move: return move; - case CollabRight::Delete: return erase; - case CollabRight::Hide: return hide; - case CollabRight::ManageLayers: return manageLayers; - case CollabRight::COUNT: break; - } - return CollabTriState::Inherit; -} - -void CollabPermissionSet::set(CollabRight right, CollabTriState state) { - switch(right) { - case CollabRight::Draw: draw = state; break; - case CollabRight::Move: move = state; break; - case CollabRight::Delete: erase = state; break; - case CollabRight::Hide: hide = state; break; - case CollabRight::ManageLayers: manageLayers = state; break; - case CollabRight::COUNT: break; - } -} - -bool CollabPermissionSet::any_concrete() const { - return draw != CollabTriState::Inherit - || move != CollabTriState::Inherit - || erase != CollabTriState::Inherit - || hide != CollabTriState::Inherit - || manageLayers != CollabTriState::Inherit; -} - -CollabPermissionSet CollabPermissionSet::all_allow() { - CollabPermissionSet s; - s.draw = CollabTriState::Allow; - s.move = CollabTriState::Allow; - s.erase = CollabTriState::Allow; - s.hide = CollabTriState::Allow; - s.manageLayers = CollabTriState::Allow; - return s; -} - -CollabPermissionSet CollabPermissionSet::all_inherit() { - return CollabPermissionSet{}; -} - -CollabPermissionSet CollabPermissionSet::content_deny() { - CollabPermissionSet s; - s.draw = CollabTriState::Deny; - s.move = CollabTriState::Deny; - s.erase = CollabTriState::Deny; - return s; -} - -bool collab_resolve_allow( - CollabRight right, - const CollabPermissionSet* personLayerOverride, - const CollabPermissionSet& layerDefaults, - const CollabPermissionSet& personDefaults, - const CollabPermissionSet& worldDefault) -{ - auto pick = [&](const CollabPermissionSet& set) -> CollabTriState { - return set.get(right); - }; - if(personLayerOverride) { - CollabTriState s = pick(*personLayerOverride); - if(s != CollabTriState::Inherit) - return s == CollabTriState::Allow; - } - { - CollabTriState s = pick(layerDefaults); - if(s != CollabTriState::Inherit) - return s == CollabTriState::Allow; - } - { - CollabTriState s = pick(personDefaults); - if(s != CollabTriState::Inherit) - return s == CollabTriState::Allow; - } - CollabTriState s = pick(worldDefault); - // World Inherit → Allow (open by default). - return s != CollabTriState::Deny; -} - -const char* collab_right_label(CollabRight right) { - switch(right) { - case CollabRight::Draw: return "Draw"; - case CollabRight::Move: return "Move"; - case CollabRight::Delete: return "Delete"; - case CollabRight::Hide: return "Hide"; - case CollabRight::ManageLayers: return "Layers"; - case CollabRight::COUNT: break; - } - return "?"; -} - -const char* collab_tristate_label(CollabTriState state) { - switch(state) { - case CollabTriState::Inherit: return "Inherit"; - case CollabTriState::Allow: return "Allow"; - case CollabTriState::Deny: return "Deny"; - } - return "?"; -} - -CollabTriState collab_cycle_tristate(CollabTriState state) { - switch(state) { - case CollabTriState::Inherit: return CollabTriState::Allow; - case CollabTriState::Allow: return CollabTriState::Deny; - case CollabTriState::Deny: return CollabTriState::Inherit; - } - return CollabTriState::Inherit; -} diff --git a/src/CollabPermissions.hpp b/src/CollabPermissions.hpp deleted file mode 100644 index 5f5e0c5..0000000 --- a/src/CollabPermissions.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * InfiniPaint (fork) - * Collab ACL: World → Person → Layer → Person×Layer (Allow/Deny/Inherit) - */ -#pragma once - -#include -#include -#include -#include -#include - -enum class CollabRight : uint8_t { - Draw = 0, - Move, - Delete, - Hide, - ManageLayers, - COUNT -}; - -enum class CollabTriState : uint8_t { - Inherit = 0, - Allow = 1, - Deny = 2 -}; - -struct CollabPermissionSet { - CollabTriState draw = CollabTriState::Inherit; - CollabTriState move = CollabTriState::Inherit; - CollabTriState erase = CollabTriState::Inherit; - CollabTriState hide = CollabTriState::Inherit; - CollabTriState manageLayers = CollabTriState::Inherit; - - CollabTriState get(CollabRight right) const; - void set(CollabRight right, CollabTriState state); - bool any_concrete() const; - - static CollabPermissionSet all_allow(); - static CollabPermissionSet all_inherit(); - static CollabPermissionSet content_deny(); // Draw/Move/Delete Deny, rest Inherit - - template - void serialize(Archive& a) { - a(draw, move, erase, hide, manageLayers); - } - - bool operator==(const CollabPermissionSet&) const = default; -}; - -struct CollabSessionDefaults { - // Concrete fallback when everything inherits (Inherit treated as Allow). - CollabPermissionSet worldDefault = CollabPermissionSet::all_allow(); - // Copied onto ClientData when a player joins. - CollabPermissionSet newPlayerDefaults = CollabPermissionSet::all_inherit(); - // Copied onto new layers' DisplayData.layerDefaults. - CollabPermissionSet newLayerDefaults = CollabPermissionSet::all_inherit(); - - template - void serialize(Archive& a) { - a(worldDefault, newPlayerDefaults, newLayerDefaults); - } - - bool operator==(const CollabSessionDefaults&) const = default; -}; - -/** Resolve Allow/Deny for peer on a layer. Host callers should short-circuit before this. */ -bool collab_resolve_allow( - CollabRight right, - const CollabPermissionSet* personLayerOverride, // nullable - const CollabPermissionSet& layerDefaults, - const CollabPermissionSet& personDefaults, - const CollabPermissionSet& worldDefault); - -const char* collab_right_label(CollabRight right); -const char* collab_tristate_label(CollabTriState state); -CollabTriState collab_cycle_tristate(CollabTriState state); diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index 485b2fb..97fa77b 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -48,8 +48,6 @@ #include #include "../CanvasComponents/ImageCanvasComponent.hpp" #include "Layers/DrawingProgramLayer.hpp" -#include "../CollabPermissions.hpp" -#include #include "../ImagePreprocess.hpp" #include #include @@ -310,44 +308,7 @@ std::optional DrawingProgram::get_text_box_start return drawTool->get_text_box_start_info(); } -void DrawingProgram::register_collab_defaults_class(World& world) { - world.delayedUpdateObjectManager.register_class(world.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors{ - .readUpdate = [](CollabSessionDefaults& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { - CollabSessionDefaults incoming; - a(incoming); - // Clients cannot escalate session defaults. - if(c) - return; - o = incoming; - }, - .postUpdateFunc = [&world](CollabSessionDefaults&) { - world.drawProg.layerMan.ensure_editing_layer_is_editable(); - world.set_to_layout_gui_if_focus(); - } - }); -} - -void DrawingProgram::ensure_collab_defaults() { - if(!collabDefaults) - collabDefaults = world.netObjMan.make_obj(); -} - -const CollabSessionDefaults& DrawingProgram::collab_defaults() const { - static const CollabSessionDefaults kFallback{}; - return collabDefaults ? *collabDefaults : kFallback; -} - -void DrawingProgram::set_collab_defaults(const CollabSessionDefaults& defaults) { - ensure_collab_defaults(); - if(*collabDefaults == defaults) - return; - *collabDefaults = defaults; - world.delayedUpdateObjectManager.send_update_to_all(collabDefaults, false); - layerMan.ensure_editing_layer_is_editable(); -} - void DrawingProgram::server_init_no_file() { - ensure_collab_defaults(); layerMan.server_init_no_file(); } @@ -361,13 +322,10 @@ void DrawingProgram::scale_up(const WorldScalar& scaleUpAmount) { } void DrawingProgram::write_components_server(cereal::PortableBinaryOutputArchive& a) { - ensure_collab_defaults(); - collabDefaults.write_create_message(a); layerMan.write_components_server(a); } void DrawingProgram::read_components_client(cereal::PortableBinaryInputArchive& a) { - collabDefaults = world.netObjMan.read_create_message(a, nullptr); layerMan.read_components_client(a); drawCache.build({}); } @@ -377,7 +335,6 @@ void DrawingProgram::init_server_callbacks() { std::vector> transforms; message(transforms); - world.currentNetUpdateClient = client; auto clientData = world.netObjMan.get_obj_temporary_ref_from_id(NetworkingObjects::NetObjID(client->customID)); if(clientData->get_grid_size() < world.ownClientData->get_grid_size()) { WorldScalar scaleUpAmount = get_canvas_scale_up_amount(world.ownClientData->get_grid_size(), clientData->get_grid_size()); @@ -392,13 +349,12 @@ void DrawingProgram::init_server_callbacks() { auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id(netID); if(!objPtr) continue; - if(!layerMan.client_may_modify_component(objPtr.get(), CollabRight::Move)) { + if(!layerMan.client_may_modify_component(objPtr.get())) { rejectedRestore.emplace_back(netID, objPtr->coords); continue; } allowed.emplace_back(netID, coord); } - world.currentNetUpdateClient = nullptr; if(!allowed.empty()) { process_transform_message(allowed); world.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_MANY_COMPONENTS, allowed); @@ -421,16 +377,15 @@ void DrawingProgram::init_server_callbacks() { world.netServer->add_recv_callback(SERVER_SET_COMPONENT_GROUP_IDS, [&](std::shared_ptr client, cereal::PortableBinaryInputArchive& message) { std::vector> groupIds; message(groupIds); - world.currentNetUpdateClient = client; + (void)client; std::vector> allowed; allowed.reserve(groupIds.size()); for(auto& [netID, groupId] : groupIds) { auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id(netID); - if(!objPtr || !layerMan.client_may_modify_component(objPtr.get(), CollabRight::Move)) + if(!objPtr || !layerMan.client_may_modify_component(objPtr.get())) continue; allowed.emplace_back(netID, groupId); } - world.currentNetUpdateClient = nullptr; if(allowed.empty()) return; process_group_id_message(allowed); @@ -511,7 +466,7 @@ void DrawingProgram::send_transforms_for(const std::vectorobj) continue; - if(!world.netObjMan.is_server() && !layerMan.own_peer_may(CollabRight::Move, obj->obj->parentLayer)) + if(!world.netObjMan.is_server() && !layerMan.client_may_modify_component(obj->obj.get())) continue; transforms.emplace_back(obj->obj.get_net_id(), obj->obj->coords); } @@ -1211,16 +1166,11 @@ void DrawingProgram::draw_drag_circle(SkCanvas* canvas, const Vector2f& sPos, co void DrawingProgram::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) { if(version >= VersionNumber(0, 6, 0)) a(controls.lockedCameraScale); - ensure_collab_defaults(); - if(version >= VersionNumber(0, 9, 0)) - a(*collabDefaults); layerMan.load_file(a, version); } void DrawingProgram::save_file(cereal::PortableBinaryOutputArchive& a) const { a(controls.lockedCameraScale); - const_cast(this)->ensure_collab_defaults(); - a(*collabDefaults); layerMan.save_file(a); } diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index 4285ca7..9138a4b 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -37,7 +37,6 @@ #include #include "Layers/DrawingProgramLayerManager.hpp" #include "DrawingProgramSelection.hpp" -#include "../CollabPermissions.hpp" class World; class PhoneDrawingProgramScreen; @@ -72,12 +71,6 @@ class DrawingProgram { DrawingProgramCache drawCache; DrawingProgramLayerManager layerMan; - /** Host-owned session ACL defaults (world / new player / new layer). */ - NetworkingObjects::NetObjOwnerPtr collabDefaults; - void ensure_collab_defaults(); - const CollabSessionDefaults& collab_defaults() const; - void set_collab_defaults(const CollabSessionDefaults& defaults); - static void register_collab_defaults_class(World& world); Vector4f* get_foreground_color_ptr(); void switch_to_tool(DrawingProgramToolType newToolType, bool force = false); diff --git a/src/DrawingProgram/DrawingProgramSelection.cpp b/src/DrawingProgram/DrawingProgramSelection.cpp index c9a3595..00d20c6 100644 --- a/src/DrawingProgram/DrawingProgramSelection.cpp +++ b/src/DrawingProgram/DrawingProgramSelection.cpp @@ -364,10 +364,6 @@ void DrawingProgramSelection::expand_selection_with_groups() { for(auto* c : drawP.layerMan.get_flattened_component_list()) { if(c->obj->groupId != 0 && groupIds.contains(c->obj->groupId) && !already.contains(c) && drawP.layerMan.can_current_peer_edit_layer(c->obj->parentLayer)) { - // Same as click/lasso select: must leave the draw cache, otherwise - // transforms move the selection draw while the BVH surface stays put - // (looks like size-dependent drift between group members). - drawP.drawCache.erase_component(c); selectedSet.push_back(c); already.insert(c); } diff --git a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp index 7ef51c3..994de1f 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp @@ -40,10 +40,9 @@ void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListIt if(c->obj->get_comp().get_type() == CanvasComponentType::IMAGE) layerMan.drawP.updateableComponents.emplace(&(*c)); - // Live ACL / fog enforcement on host + // Live fog / lock enforcement on host if(layerMan.addToCacheOnComponentInsert && layerMan.drawP.world.netServer) { - if(layerMan.drawP.world.currentNetUpdateClient - && !layerMan.current_net_client_may(CollabRight::Draw, &layerListItem)) { + if(layerMan.drawP.world.currentNetUpdateClient && layerListItem.is_player_edit_blocked()) { layerMan.defer_reject_component(&layerListItem, c->obj.get_net_id()); return; } diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp index a849c61..2937aa6 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp @@ -19,13 +19,10 @@ #include "DrawingProgramLayerListItem.hpp" #include "DrawingProgramLayerFolder.hpp" #include -#include #include "DrawingProgramLayer.hpp" -#include "FogStripper.hpp" #include "../../World.hpp" #include "../../MainProgram.hpp" #include "SerializedBlendMode.hpp" -#include "../../DrawingProgram/DrawingProgram.hpp" using namespace NetworkingObjects; @@ -181,39 +178,17 @@ DrawingProgramLayer& DrawingProgramLayerListItem::get_layer() const { return *layerData; } -void DrawingProgramLayerListItem::DisplayData::migrate_player_locked_into_defaults() { - if(playerLocked && !fogLayer) - layerDefaults = CollabPermissionSet::content_deny(); -} - -void DrawingProgramLayerListItem::DisplayData::sync_player_locked_from_defaults() { - if(fogLayer) { - playerLocked = true; - return; - } - playerLocked = layerDefaults.draw == CollabTriState::Deny - && layerDefaults.move == CollabTriState::Deny - && layerDefaults.erase == CollabTriState::Deny; -} - void DrawingProgramLayerListItem::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, DrawingProgramLayerManager& layerMan) { nameData = layerMan.drawP.world.netObjMan.make_obj(); a(*nameData); displayData = layerMan.drawP.world.netObjMan.make_obj(); - if(version >= VersionNumber(0, 9, 0)) { + if(version >= VersionNumber(0, 8, 0)) { a(*displayData); + // Normalize cereal bools (non-0/1 bytes must not stick as "true" forever). displayData->visible = displayData->visible ? true : false; displayData->playerLocked = displayData->playerLocked ? true : false; displayData->fogLayer = displayData->fogLayer ? true : false; - displayData->sync_player_locked_from_defaults(); - } - else if(version >= VersionNumber(0, 8, 0)) { - a(displayData->alpha, displayData->visible, displayData->blendMode, displayData->playerLocked, displayData->fogLayer); - displayData->visible = displayData->visible ? true : false; - displayData->playerLocked = displayData->playerLocked ? true : false; - displayData->fogLayer = displayData->fogLayer ? true : false; - displayData->migrate_player_locked_into_defaults(); } else { a(displayData->alpha, displayData->visible, displayData->blendMode); @@ -283,11 +258,6 @@ const std::string& DrawingProgramLayerListItem::get_name() const { } void DrawingProgramLayerListItem::set_alpha(DrawingProgramLayerManager& layerMan, float newAlpha) const { - // Players must not fade fog out — same authority as visibility. - if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world)) - return; - if(!FogStripper::world_is_host(layerMan.drawP.world) && !layerMan.own_peer_may(CollabRight::Hide, this)) - return; if(displayData && displayData->alpha != newAlpha) { displayData->alpha = newAlpha; layerMan.drawP.drawCache.clear_own_cached_surfaces(); @@ -300,11 +270,6 @@ float DrawingProgramLayerListItem::get_alpha() const { } void DrawingProgramLayerListItem::set_visible(DrawingProgramLayerManager& layerMan, bool newVisible) const { - // Fog of War is host-controlled; clients hiding it would sync and strip FoW for everyone. - if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world)) - return; - if(!FogStripper::world_is_host(layerMan.drawP.world) && !layerMan.own_peer_may(CollabRight::Hide, this)) - return; if(displayData && displayData->visible != newVisible) { displayData->visible = newVisible; layerMan.drawP.drawCache.clear_own_cached_surfaces(); @@ -332,23 +297,12 @@ void DrawingProgramLayerListItem::set_player_locked(DrawingProgramLayerManager& if(!displayData) return; bool newLocked = locked || displayData->fogLayer; - CollabPermissionSet nextDefaults = displayData->layerDefaults; - if(newLocked) - nextDefaults = CollabPermissionSet::content_deny(); - else if(displayData->playerLocked) { - // Unlock: clear content Deny back to Inherit for Draw/Move/Delete. - nextDefaults.draw = CollabTriState::Inherit; - nextDefaults.move = CollabTriState::Inherit; - nextDefaults.erase = CollabTriState::Inherit; + if(displayData->playerLocked != newLocked) { + displayData->playerLocked = newLocked; + layerMan.drawP.drawCache.clear_own_cached_surfaces(); + layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); + layerMan.refresh_fog_client_projection(); } - if(displayData->playerLocked == newLocked && displayData->layerDefaults == nextDefaults) - return; - displayData->playerLocked = newLocked; - displayData->layerDefaults = nextDefaults; - layerMan.drawP.drawCache.clear_own_cached_surfaces(); - layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); - layerMan.refresh_fog_client_projection(); - layerMan.ensure_editing_layer_is_editable(); } bool DrawingProgramLayerListItem::get_player_locked() const { @@ -374,55 +328,6 @@ bool DrawingProgramLayerListItem::is_player_edit_blocked() const { return is_fog_layer() || get_player_locked(); } -const CollabPermissionSet& DrawingProgramLayerListItem::get_layer_defaults() const { - static const CollabPermissionSet kInherit = CollabPermissionSet::all_inherit(); - return displayData ? displayData->layerDefaults : kInherit; -} - -void DrawingProgramLayerListItem::set_layer_defaults(DrawingProgramLayerManager& layerMan, const CollabPermissionSet& defaults) const { - if(!displayData || displayData->layerDefaults == defaults) - return; - displayData->layerDefaults = defaults; - displayData->sync_player_locked_from_defaults(); - layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); - layerMan.ensure_editing_layer_is_editable(); -} - -const std::map& DrawingProgramLayerListItem::get_person_layer_overrides() const { - static const std::map kEmpty; - return displayData ? displayData->personOverrides : kEmpty; -} - -void DrawingProgramLayerListItem::set_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName, const CollabPermissionSet& overrides) const { - if(!displayData || displayName.empty()) - return; - if(!overrides.any_concrete()) { - clear_person_layer_override(layerMan, displayName); - return; - } - auto it = displayData->personOverrides.find(displayName); - if(it != displayData->personOverrides.end() && it->second == overrides) - return; - displayData->personOverrides[displayName] = overrides; - layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); - layerMan.ensure_editing_layer_is_editable(); -} - -void DrawingProgramLayerListItem::clear_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName) const { - if(!displayData) - return; - if(displayData->personOverrides.erase(displayName) == 0) - return; - layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); - layerMan.ensure_editing_layer_is_editable(); -} - -void DrawingProgramLayerListItem::apply_new_layer_permission_defaults(DrawingProgramLayerManager& layerMan) const { - if(!displayData || is_folder()) - return; - set_layer_defaults(layerMan, layerMan.drawP.collab_defaults().newLayerDefaults); -} - void DrawingProgramLayerListItem::set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo) { set_blend_mode(layerMan, metaInfo.blendMode); set_alpha(layerMan, metaInfo.alpha); @@ -488,27 +393,6 @@ void DrawingProgramLayerListItem::register_class(World& w) { } }); w.delayedUpdateObjectManager.register_class(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors{ - .readUpdate = [](DisplayData& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { - DisplayData incoming; - a(incoming); - // Client→host: never allow stripping fog, unlocking, hiding, fading fog, or escalating ACL. - if(c) { - if(o.fogLayer || incoming.fogLayer) { - incoming.fogLayer = true; - incoming.playerLocked = true; - incoming.visible = true; - incoming.alpha = o.alpha; - } - else { - incoming.fogLayer = false; - incoming.playerLocked = o.playerLocked; - } - incoming.layerDefaults = o.layerDefaults; - incoming.personOverrides = o.personOverrides; - } - o = incoming; - o.sync_player_locked_from_defaults(); - }, .postUpdateFunc = [&](DisplayData& o) { (void)o; w.drawProg.drawCache.clear_own_cached_surfaces(); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp index 2b99695..9f13dab 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp @@ -20,12 +20,10 @@ #include #include #include -#include #include #include "DrawingProgramLayerFolder.hpp" #include "DrawingProgramLayer.hpp" #include "SerializedBlendMode.hpp" -#include "../../CollabPermissions.hpp" #include "../../CanvasComponents/CanvasComponentContainer.hpp" class DrawingProgramLayerFolder; @@ -91,14 +89,6 @@ class DrawingProgramLayerListItem { // Fog layers are always treated as player-locked for edit checks. bool is_player_edit_blocked() const; - const CollabPermissionSet& get_layer_defaults() const; - void set_layer_defaults(DrawingProgramLayerManager& layerMan, const CollabPermissionSet& defaults) const; - const std::map& get_person_layer_overrides() const; - void set_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName, const CollabPermissionSet& overrides) const; - void clear_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName) const; - /** Apply session new-layer defaults (call after create). */ - void apply_new_layer_permission_defaults(DrawingProgramLayerManager& layerMan) const; - void set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo); DrawingProgramLayerListItemMetaInfo get_metainfo() const; @@ -125,13 +115,9 @@ class DrawingProgramLayerListItem { SerializedBlendMode blendMode = SerializedBlendMode::BLEND_SRC_OVER; bool playerLocked = false; bool fogLayer = false; - CollabPermissionSet layerDefaults = CollabPermissionSet::all_inherit(); - std::map personOverrides; template void serialize(Archive& a) { - a(alpha, visible, blendMode, playerLocked, fogLayer, layerDefaults, personOverrides); + a(alpha, visible, blendMode, playerLocked, fogLayer); } - void migrate_player_locked_into_defaults(); - void sync_player_locked_from_defaults(); }; NetworkingObjects::NetObjOwnerPtr nameData; NetworkingObjects::NetObjOwnerPtr displayData; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp index 2ae95ab..d1a6947 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp @@ -19,7 +19,6 @@ #include "DrawingProgramLayerManager.hpp" #include "FogStripper.hpp" #include "../../World.hpp" -#include "../../ClientData.hpp" #include "Helpers/NetworkingObjects/NetObjID.hpp" #include "Helpers/NetworkingObjects/NetObjOrderedList.hpp" #include "Helpers/NetworkingObjects/NetObjOwnerPtr.decl.hpp" @@ -292,88 +291,20 @@ void DrawingProgramLayerManager::disable_add_to_cache_and_commit_update_block(co addToCacheOnComponentInsert = true; } -bool DrawingProgramLayerManager::peer_may(CollabRight right, const DrawingProgramLayerListItem* layer, const ClientData* peer) const { +bool DrawingProgramLayerManager::can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const { if(!layer) return false; - if(FogStripper::world_is_host(drawP.world) && (!peer || peer == drawP.world.ownClientData.get())) - return true; - // Content rights on fog layers are always denied for non-host peers. - if(layer->is_fog_layer() - && (right == CollabRight::Draw || right == CollabRight::Move || right == CollabRight::Delete)) - return false; - if(right == CollabRight::Hide && layer->is_fog_layer()) - return false; - - CollabPermissionSet personDefaults = CollabPermissionSet::all_inherit(); - const CollabPermissionSet* personLayer = nullptr; - std::string peerName; - if(peer) { - personDefaults = peer->get_person_defaults(); - peerName = peer->get_display_name(); - auto& overrides = layer->get_person_layer_overrides(); - auto it = overrides.find(peerName); - if(it != overrides.end()) - personLayer = &it->second; - } - return collab_resolve_allow( - right, - personLayer, - layer->get_layer_defaults(), - personDefaults, - drawP.collab_defaults().worldDefault); -} - -bool DrawingProgramLayerManager::own_peer_may(CollabRight right, const DrawingProgramLayerListItem* layer) const { if(FogStripper::world_is_host(drawP.world)) return true; - return peer_may(right, layer, drawP.world.ownClientData.get()); -} - -bool DrawingProgramLayerManager::peer_may_manage_layers(const ClientData* peer) const { - if(FogStripper::world_is_host(drawP.world) && (!peer || peer == drawP.world.ownClientData.get())) - return true; - // ManageLayers is not layer-scoped — resolve with empty layer defaults / no person×layer. - CollabPermissionSet personDefaults = peer ? peer->get_person_defaults() : CollabPermissionSet::all_inherit(); - return collab_resolve_allow( - CollabRight::ManageLayers, - nullptr, - CollabPermissionSet::all_inherit(), - personDefaults, - drawP.collab_defaults().worldDefault); -} - -bool DrawingProgramLayerManager::current_net_client_may(CollabRight right, const DrawingProgramLayerListItem* layer) const { - if(!drawP.world.currentNetUpdateClient) - return true; // local/host-originated - auto peer = drawP.world.netObjMan.get_obj_temporary_ref_from_id( - NetworkingObjects::NetObjID(drawP.world.currentNetUpdateClient->customID)); - if(!peer) - return false; - if(right == CollabRight::ManageLayers) - return peer_may_manage_layers(peer.get()); - return peer_may(right, layer, peer.get()); -} - -bool DrawingProgramLayerManager::can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const { - return own_peer_may(CollabRight::Draw, layer); + return !layer->is_player_edit_blocked(); } bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp) const { - return client_may_modify_component(comp, CollabRight::Draw); -} - -bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp, CollabRight right) const { // Host-side authority check for CLIENT messages — must NOT use can_current_peer_edit_layer // (that always returns true on the host). if(!comp || !comp->parentLayer) return false; - if(!drawP.world.currentNetUpdateClient) { - // Transform callbacks set currentNetUpdateClient; fallback: legacy lock/fog. - if(comp->parentLayer->is_player_edit_blocked()) - return false; - return true; - } - return current_net_client_may(right, comp->parentLayer); + return !comp->parentLayer->is_player_edit_blocked(); } void DrawingProgramLayerManager::ensure_editing_layer_is_editable() { diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp index a0bcda3..6c22977 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp @@ -19,12 +19,9 @@ #pragma once #include "DrawingProgramLayerListItem.hpp" #include "DrawingProgramLayerManagerGUI.hpp" -#include "../../CollabPermissions.hpp" #include #include -class ClientData; - class DrawingProgramLayerManager { private: DrawingProgram& drawP; @@ -92,11 +89,6 @@ class DrawingProgramLayerManager { bool can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const; bool client_may_modify_component(const CanvasComponentContainer* comp) const; - bool client_may_modify_component(const CanvasComponentContainer* comp, CollabRight right) const; - bool own_peer_may(CollabRight right, const DrawingProgramLayerListItem* layer) const; - bool peer_may(CollabRight right, const DrawingProgramLayerListItem* layer, const ClientData* peer) const; - bool peer_may_manage_layers(const ClientData* peer) const; - bool current_net_client_may(CollabRight right, const DrawingProgramLayerListItem* layer) const; void ensure_editing_layer_is_editable(); /** Host: recompute fog coverage and erase/reveal (+ resources) on clients. */ void refresh_fog_client_projection(); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp index 32a6020..30f4bfd 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp @@ -26,8 +26,6 @@ #include "Helpers/ConvertVec.hpp" #include "Helpers/NetworkingObjects/NetObjOrderedList.hpp" #include "SerializedBlendMode.hpp" -#include "../../CollabPermissions.hpp" -#include "../../ClientData.hpp" #include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp" #include "../../GUIStuff/ElementHelpers/ButtonHelpers.hpp" @@ -146,28 +144,23 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { }); } gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { - // Fog stays visible for players — eye is informational only on clients. - const bool fogVisibilityLocked = layer->is_fog_layer() && !FogStripper::world_is_host(world); svg_icon_button(gui, "visible button", layer->get_visible() ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", { .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, .size = TreeListing::ENTRY_HEIGHT, - .onClick = [&, layer, fogVisibilityLocked] { - if(!fogVisibilityLocked && layerMan.own_peer_may(CollabRight::Hide, layer.get())) - layer->set_visible(layerMan, !layer->get_visible()); + .onClick = [&, layer] { + layer->set_visible(layerMan, !layer->get_visible()); } }); }); - if(layerMan.peer_may_manage_layers(world.ownClientData.get())) { - gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { - svg_icon_button(gui, "delete button", "data/icons/trash.svg", { - .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, - .size = TreeListing::ENTRY_HEIGHT, - .onClick = [&, objIndex] { - remove_layer(objIndex); - } - }); + gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { + svg_icon_button(gui, "delete button", "data/icons/trash.svg", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .size = TreeListing::ENTRY_HEIGHT, + .onClick = [&, objIndex] { + remove_layer(objIndex); + } }); - } + }); }, .onDoubleClick = [&](const TreeListingObjIndexList& objIndex) { auto layer = get_layer_from_obj_index(objIndex); @@ -200,8 +193,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { } }, .moveObj = [&](const std::vector& objectIndicesTreeListing, const TreeListingObjIndexList& newObjIndexTreeListing) { - if(!layerMan.peer_may_manage_layers(world.ownClientData.get())) - return; NetObjID listObj; size_t index; struct ParentObjectIDPair { @@ -402,34 +393,32 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { }); } - if(layerMan.peer_may_manage_layers(world.ownClientData.get())) { - left_to_right_line_layout(gui, [&]() { - input_text(gui, "input new name", &nameForNew, { - .onEnter = [&] { - auto newLayerObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, false)); - layerMan.editingLayer = newLayerObjInfo->obj; - layerMan.drawP.world.main.g.gui.set_to_layout(); - } - }); - - svg_icon_button(gui, "create new layer", "data/icons/plusbold.svg", { - .size = SMALL_BUTTON_SIZE, - .onClick = [&] { - auto newLayerObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, false)); - layerMan.editingLayer = newLayerObjInfo->obj; - layerMan.drawP.world.main.g.gui.set_to_layout(); - } - }); - - svg_icon_button(gui, "create new folder", "data/icons/folderbold.svg", { - .size = SMALL_BUTTON_SIZE, - .onClick = [&] { - auto newFolderObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, true)); - newFolderObjInfo->obj->get_folder().isFolderOpen = true; - } - }); + left_to_right_line_layout(gui, [&]() { + input_text(gui, "input new name", &nameForNew, { + .onEnter = [&] { + auto newLayerObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, false)); + layerMan.editingLayer = newLayerObjInfo->obj; + layerMan.drawP.world.main.g.gui.set_to_layout(); + } }); - } + + svg_icon_button(gui, "create new layer", "data/icons/plusbold.svg", { + .size = SMALL_BUTTON_SIZE, + .onClick = [&] { + auto newLayerObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, false)); + layerMan.editingLayer = newLayerObjInfo->obj; + layerMan.drawP.world.main.g.gui.set_to_layout(); + } + }); + + svg_icon_button(gui, "create new folder", "data/icons/folderbold.svg", { + .size = SMALL_BUTTON_SIZE, + .onClick = [&] { + auto newFolderObjInfo = create_layer(new DrawingProgramLayerListItem(world.netObjMan, nameForNew, true)); + newFolderObjInfo->obj->get_folder().isFolderOpen = true; + } + }); + }); auto editingLayerLock = editingLayer.lock(); if(editingLayerLock) { @@ -487,67 +476,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { if(layer) layer->set_player_locked(layerMan, !layer->get_player_locked()); }); - text_label(gui, "Layer defaults (Inherit → Person → World)"); - { - const CollabPermissionSet& layerSet = editingLayerLock->get_layer_defaults(); - left_to_right_line_layout(gui, [&]() { - for(uint8_t i = 0; i < static_cast(CollabRight::COUNT); i++) { - CollabRight right = static_cast(i); - gui.new_id(200 + i, [&] { - std::string label = std::string(collab_right_label(right)) + ":" - + collab_tristate_label(layerSet.get(right)); - text_button(gui, "layer perm", label.c_str(), { - .onClick = [&, right] { - auto layer = editingLayer.lock(); - if(!layer) - return; - CollabPermissionSet next = layer->get_layer_defaults(); - next.set(right, collab_cycle_tristate(next.get(right))); - layer->set_layer_defaults(layerMan, next); - } - }); - }); - } - }); - } - if(world.clients) { - text_label(gui, "Per-player overrides on this layer"); - size_t pnum = 0; - for(auto& client : world.clients->get_data()) { - if(client == world.ownClientData) - continue; - gui.new_id(300 + pnum++, [&] { - const std::string pname = client->get_display_name(); - text_label(gui, pname); - CollabPermissionSet overrideSet = CollabPermissionSet::all_inherit(); - auto it = editingLayerLock->get_person_layer_overrides().find(pname); - if(it != editingLayerLock->get_person_layer_overrides().end()) - overrideSet = it->second; - left_to_right_line_layout(gui, [&]() { - for(uint8_t i = 0; i < static_cast(CollabRight::COUNT); i++) { - CollabRight right = static_cast(i); - gui.new_id(i, [&] { - std::string label = std::string(collab_right_label(right)) + ":" - + collab_tristate_label(overrideSet.get(right)); - text_button(gui, "player layer perm", label.c_str(), { - .onClick = [&, right, pname] { - auto layer = editingLayer.lock(); - if(!layer) - return; - CollabPermissionSet next = CollabPermissionSet::all_inherit(); - auto found = layer->get_person_layer_overrides().find(pname); - if(found != layer->get_person_layer_overrides().end()) - next = found->second; - next.set(right, collab_cycle_tristate(next.get(right))); - layer->set_person_layer_override(layerMan, pname, next); - } - }); - }); - } - }); - }); - } - } } else text_label(gui, "Fog layers are always locked for players."); @@ -632,7 +560,6 @@ NetworkingObjects::NetObjOrderedListIterator Drawin auto insertedLayerPair = create_in_proper_position(newItem); auto& it = insertedLayerPair.second; - it->obj->apply_new_layer_permission_defaults(layerMan); world.undo.push(std::make_unique(std::make_unique(it->obj->get_undo_data(world.undo)), it->pos, world.undo.get_undoid_from_netid(insertedLayerPair.first), world.undo.get_undoid_from_netid(it->obj.get_net_id()))); world.main.g.gui.set_to_layout(); refresh_gui_data(); diff --git a/src/GlobalConfig.cpp b/src/GlobalConfig.cpp index a170616..a6efc1a 100644 --- a/src/GlobalConfig.cpp +++ b/src/GlobalConfig.cpp @@ -251,6 +251,6 @@ void GlobalConfig::load_licenses() { std::sort(thirdPartyLicenses.begin(), thirdPartyLicenses.end(), [](const auto& a1, const auto& a2) { return std::lexicographical_compare(a1.first.begin(), a1.first.end(), a2.first.begin(), a2.first.end()); }); - ownLicenseText = "InfiniPaint v" + VersionConstants::full_version_string() + "\n"; + ownLicenseText = "InfiniPaint v" + VersionConstants::CURRENT_VERSION_STRING + "\n"; ownLicenseText += read_file_to_string("data/license"); } diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index ab3d9e6..0c4f4da 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -51,8 +51,6 @@ #endif #include -#include "VersionConstants.hpp" -#include "WindowsPortableUpdater.hpp" #ifdef __ANDROID__ #define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersionAndroid.txt" @@ -123,95 +121,18 @@ void MainProgram::update() { post_callback(); } -void MainProgram::start_windows_portable_update_download() { -#ifdef _WIN32 - if(updateCheckerData.downloadingZip || updateCheckerData.downloadUrl.empty()) - return; - updateCheckerData.downloadingZip = true; - updateCheckerData.downloadStatus = "Downloading update…"; - updateCheckerData.zipDownload = FileDownloader::download_data_from_url(updateCheckerData.downloadUrl); - g.gui.set_to_layout(); -#else - (void)0; -#endif -} - void MainProgram::update_notification_check() { #ifndef __EMSCRIPTEN__ -#ifdef _WIN32 - // Poll in-flight zip download / apply. - if(updateCheckerData.downloadingZip && updateCheckerData.zipDownload) { - switch(updateCheckerData.zipDownload->status) { - case FileDownloader::DownloadData::Status::IN_PROGRESS: { - float p = updateCheckerData.zipDownload->progress.load(); - if(p > 0.f) - updateCheckerData.downloadStatus = "Downloading update… " + std::to_string(int(p * 100.f)) + "%"; - break; - } - case FileDownloader::DownloadData::Status::SUCCESS: { - updateCheckerData.downloadStatus = "Installing update…"; - g.gui.set_to_layout(); - if(!WindowsPortableUpdater::apply_zip_and_restart(updateCheckerData.zipDownload->str)) { - updateCheckerData.downloadStatus = "Update install failed (see log)."; - updateCheckerData.downloadingZip = false; - updateCheckerData.zipDownload = nullptr; - g.gui.set_to_layout(); - } - break; - } - case FileDownloader::DownloadData::Status::FAILURE: - updateCheckerData.downloadStatus = "Download failed."; - updateCheckerData.downloadingZip = false; - updateCheckerData.zipDownload = nullptr; - g.gui.set_to_layout(); - break; - } - } -#endif - if(!updateCheckerData.updateCheckDone) { if(conf.checkForUpdates) { - if(!updateCheckerData.versionFile) { -#ifdef _WIN32 - // Fork continuous builds: compare git SHA against Forgejo windows-continuous. - updateCheckerData.forgejoContinuous = true; - updateCheckerData.versionFile = FileDownloader::download_data_from_url(FORGEJO_WINDOWS_CONTINUOUS_API); -#else + if(!updateCheckerData.versionFile) updateCheckerData.versionFile = FileDownloader::download_data_from_url(UPDATE_NOTIFICATION_URL); -#endif - } else { switch(updateCheckerData.versionFile->status) { case FileDownloader::DownloadData::Status::IN_PROGRESS: break; case FileDownloader::DownloadData::Status::SUCCESS: { updateCheckerData.updateCheckDone = true; -#ifdef _WIN32 - std::string label, commit, zipUrl; - if(WindowsPortableUpdater::parse_continuous_release_json( - updateCheckerData.versionFile->str, label, commit, zipUrl)) { - updateCheckerData.remoteCommit = commit; - updateCheckerData.downloadUrl = zipUrl; - updateCheckerData.newVersionStr = label.empty() - ? (VersionConstants::CURRENT_VERSION_STRING + "+" + commit) - : label; - const std::string& local = VersionConstants::build_commit_short(); - Logger::get().log(Logger::LogType::INFO, - "Forgejo continuous commit=" + commit + " local=" + local); - if(local != "unknown" && commit != local.substr(0, commit.size())) { - updateCheckerData.showGui = true; - Logger::get().log(Logger::LogType::PHONE_USERINFO, - "Windows update available: " + updateCheckerData.newVersionStr); - g.gui.set_to_layout(); - } - else if(local == "unknown") - Logger::get().log(Logger::LogType::INFO, "Local build commit unknown — skip update prompt"); - else - Logger::get().log(Logger::LogType::INFO, "Windows portable build is up to date"); - } - else - Logger::get().log(Logger::LogType::INFO, "Could not parse Forgejo windows-continuous release"); -#else std::optional newVersion = version_str_to_version_numbers(updateCheckerData.versionFile->str); std::optional currentVersion = VersionConstants::CURRENT_VERSION_NUMBER; if(newVersion.has_value() && currentVersion.has_value()) { @@ -231,7 +152,6 @@ void MainProgram::update_notification_check() { } else Logger::get().log(Logger::LogType::INFO, "Update notification file couldn't be converted to version numbers"); -#endif updateCheckerData.versionFile = nullptr; break; } diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index d855884..ee8c62e 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -64,11 +64,6 @@ class MainProgram { public: static constexpr size_t LOG_SIZE = 30; static constexpr const char* UPDATE_DOWNLOAD_URL = "https://infinipaint.com/download.html"; - /** Public Forgejo continuous Windows release (fork). */ - static constexpr const char* FORGEJO_WINDOWS_CONTINUOUS_API = - "https://forgejo.fsociety00.cc/api/v1/repos/copilot/infinipaint/releases/tags/windows-continuous"; - static constexpr const char* FORGEJO_WINDOWS_CONTINUOUS_PAGE = - "https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous"; InputManager input; @@ -203,18 +198,11 @@ class MainProgram { ~MainProgram(); void update_notification_check(); - void start_windows_portable_update_download(); struct UpdateCheckerData { bool showGui = false; bool updateCheckDone = false; std::string newVersionStr; - std::string downloadUrl; - std::string remoteCommit; - bool forgejoContinuous = false; - bool downloadingZip = false; - std::string downloadStatus; std::shared_ptr versionFile; - std::shared_ptr zipDownload; } updateCheckerData; float calculate_gui_scale(); diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 3605a26..9cc1f4d 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -36,8 +36,6 @@ #include "RichText/TextStyleModifier.hpp" #include "VersionConstants.hpp" #include "World.hpp" -#include "ClientData.hpp" -#include "CollabPermissions.hpp" #include #include #include @@ -677,57 +675,27 @@ void Toolbar::update_notification_gui() { center_obstructing_window_gui("Update notifications GUI", CLAY_SIZING_FIXED(700), CLAY_SIZING_FIT(0), [&] { gui.new_id("update notification gui", [&] { - text_label_centered(gui, "Update available!"); - text_label_centered(gui, main.updateCheckerData.newVersionStr); -#ifdef _WIN32 - if(main.updateCheckerData.forgejoContinuous) { - text_label(gui, "Local: " + VersionConstants::full_version_string()); - if(main.updateCheckerData.downloadingZip) { - text_label_centered(gui, main.updateCheckerData.downloadStatus.empty() - ? "Downloading…" - : main.updateCheckerData.downloadStatus); + text_label_centered(gui, "Update v" + main.updateCheckerData.newVersionStr + " available!"); + text_button(gui, "download", "Open download page in web browser", { + .wide = true, + .onClick = [&]{ + SDL_OpenURL(MainProgram::UPDATE_DOWNLOAD_URL); + main.updateCheckerData.showGui = false; } - else { - text_button(gui, "download install", "Download and install update", { - .wide = true, - .onClick = [&]{ - main.start_windows_portable_update_download(); - } - }); - text_button(gui, "open page", "Open Forgejo release page", { - .wide = true, - .onClick = [&]{ - SDL_OpenURL(MainProgram::FORGEJO_WINDOWS_CONTINUOUS_PAGE); - } - }); + }); + text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", { + .wide = true, + .onClick = [&]{ + main.conf.checkForUpdates = false; + main.updateCheckerData.showGui = false; } - } - else -#endif - { - text_button(gui, "download", "Open download page in web browser", { - .wide = true, - .onClick = [&]{ - SDL_OpenURL(MainProgram::UPDATE_DOWNLOAD_URL); - main.updateCheckerData.showGui = false; - } - }); - } - if(!main.updateCheckerData.downloadingZip) { - text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", { - .wide = true, - .onClick = [&]{ - main.conf.checkForUpdates = false; - main.updateCheckerData.showGui = false; - } - }); - text_button(gui, "ignore for now", "Ignore for now", { - .wide = true, - .onClick = [&]{ - main.updateCheckerData.showGui = false; - } - }); - } + }); + text_button(gui, "ignore for now", "Ignore for now", { + .wide = true, + .onClick = [&]{ + main.updateCheckerData.showGui = false; + } + }); }); }); } @@ -1237,84 +1205,33 @@ void Toolbar::color_palette(const char* id, Vector4f* color, const std::function } void Toolbar::player_list() { - using namespace GUIStuff; - using namespace ElementHelpers; auto& gui = main.g.gui; - auto& world = *main.world; - auto& layerMan = world.drawProg.layerMan; - auto draw_perm_buttons = [&](const char* rowId, const CollabPermissionSet& shown, - const std::function& cycleRight) { - gui.new_id(rowId, [&] { - left_to_right_line_layout(gui, [&]() { - for(uint8_t i = 0; i < static_cast(CollabRight::COUNT); i++) { - CollabRight right = static_cast(i); - gui.new_id(i, [&] { - std::string label = std::string(collab_right_label(right)) + ":" - + collab_tristate_label(shown.get(right)); - text_button(gui, "perm", label.c_str(), { - .onClick = [cycleRight, right] { - cycleRight(right); - } - }); - }); - } - }); - }); - }; - - center_obstructing_window_gui("player client list", CLAY_SIZING_FIT(640), CLAY_SIZING_FIT(0), [&] { + center_obstructing_window_gui("player client list", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] { gui.new_id("client list", [&] { text_label_centered(gui, "Player List"); - if(!world.clientStillConnecting) { - if(world.netServer) { + if(!main.world->clientStillConnecting) { + if(main.world->netServer) { text_button(gui, "force all jump to host", "Bring Everyone Here", { .wide = true, .onClick = [&] { - world.drawProg.force_clients_to_own_view(); + main.world->drawProg.force_clients_to_own_view(); } }); - text_button(gui, "toggle session defaults", collabSessionDefaultsOpen ? "Hide Session Defaults" : "Session Defaults…", { - .wide = true, - .onClick = [&] { - collabSessionDefaultsOpen = !collabSessionDefaultsOpen; - } - }); - if(collabSessionDefaultsOpen) { - const CollabSessionDefaults& cur = world.drawProg.collab_defaults(); - text_label(gui, "World default (fallback when everything inherits)"); - draw_perm_buttons("world default row", cur.worldDefault, [&](CollabRight right) { - auto d = world.drawProg.collab_defaults(); - d.worldDefault.set(right, collab_cycle_tristate(d.worldDefault.get(right))); - world.drawProg.set_collab_defaults(d); - }); - text_label(gui, "New player defaults (copied on join)"); - draw_perm_buttons("new player default row", cur.newPlayerDefaults, [&](CollabRight right) { - auto d = world.drawProg.collab_defaults(); - d.newPlayerDefaults.set(right, collab_cycle_tristate(d.newPlayerDefaults.get(right))); - world.drawProg.set_collab_defaults(d); - }); - text_label(gui, "New layer defaults (copied on create)"); - draw_perm_buttons("new layer default row", cur.newLayerDefaults, [&](CollabRight right) { - auto d = world.drawProg.collab_defaults(); - d.newLayerDefaults.set(right, collab_cycle_tristate(d.newLayerDefaults.get(right))); - world.drawProg.set_collab_defaults(d); - }); - } } left_to_right_line_layout(gui, [&]() { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} }, - .backgroundColor = convert_vec4(SkColor4f{world.ownClientData->get_cursor_color().x(), world.ownClientData->get_cursor_color().y(), world.ownClientData->get_cursor_color().z(), 1.0f}), + .backgroundColor = convert_vec4(SkColor4f{main.world->ownClientData->get_cursor_color().x(), main.world->ownClientData->get_cursor_color().y(), main.world->ownClientData->get_cursor_color().z(), 1.0f}), .cornerRadius = CLAY_CORNER_RADIUS(3) }) {} - text_label(gui, world.ownClientData->get_display_name() + " (you / host)"); + text_label(gui, main.world->ownClientData->get_display_name()); }); size_t num = 0; - for(auto& client : world.clients->get_data()) { - if(client != world.ownClientData) { + for(auto& client : main.world->clients->get_data()) { + if(client != main.world->ownClientData) { gui.new_id(num++, [&] { left_to_right_line_layout(gui, [&]() { CLAY_AUTO_ID({ @@ -1327,40 +1244,15 @@ void Toolbar::player_list() { text_label(gui, client->get_display_name()); CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} text_button(gui, "teleport button", "Jump To", { .onClick = [&] { - world.drawData.cam.smooth_move_to(world, client->get_cam_coords(), client->get_window_size()); + main.world->drawData.cam.smooth_move_to(*main.world, client->get_cam_coords(), client->get_window_size()); }}); - if(world.netServer) { - text_button(gui, "perms button", "Rights", { - .onClick = [&, name = client->get_display_name()] { - if(collabPermsExpandedPlayer == name) - collabPermsExpandedPlayer.clear(); - else - collabPermsExpandedPlayer = name; - } - }); - } }); - if(world.netServer && collabPermsExpandedPlayer == client->get_display_name()) { - text_label(gui, "Person defaults (Inherit → World)"); - NetworkingObjects::NetObjID clientId = client.get_net_id(); - draw_perm_buttons("person defaults", client->get_person_defaults(), [&, clientId](CollabRight right) { - auto peer = world.netObjMan.get_obj_temporary_ref_from_id(clientId); - if(!peer) - return; - CollabPermissionSet next = peer->get_person_defaults(); - next.set(right, collab_cycle_tristate(next.get(right))); - ClientData::set_person_defaults(peer, next); - layerMan.ensure_editing_layer_is_editable(); - }); - } }); } } } text_button(gui, "close list", "Done", { .wide = true, .onClick = [&] { playerMenuOpen = false; - collabSessionDefaultsOpen = false; - collabPermsExpandedPlayer.clear(); }}); }); }); diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index a243e8a..05d4438 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -153,8 +153,6 @@ class Toolbar { bool menuPopUpOpen = false; bool optionsMenuOpen = false; bool playerMenuOpen = false; - bool collabSessionDefaultsOpen = false; - std::string collabPermsExpandedPlayer; float finalCalculatedGuiScale = 1.0f; diff --git a/src/VersionConstants.cpp b/src/VersionConstants.cpp index 3d41538..2138525 100644 --- a/src/VersionConstants.cpp +++ b/src/VersionConstants.cpp @@ -17,20 +17,10 @@ */ #include "VersionConstants.hpp" -#include "BuildCommit.hpp" #include namespace VersionConstants { -const std::string& build_commit_short() { - static const std::string sha = INFINIPAINT_BUILD_COMMIT; - return sha; -} - -std::string full_version_string() { - return CURRENT_VERSION_STRING + "+" + build_commit_short(); -} - VersionNumber header_to_version_number(const std::string& header) { static std::unordered_map m; if(m.empty()) { @@ -42,7 +32,6 @@ VersionNumber header_to_version_number(const std::string& header) { m["INFPNT000006"] = VersionNumber(0, 6, 0); m["INFPNT000007"] = VersionNumber(0, 7, 0); m["INFPNT000008"] = VersionNumber(0, 8, 0); - m["INFPNT000009"] = VersionNumber(0, 9, 0); } auto it = m.find(header); if(it == m.end()) diff --git a/src/VersionConstants.hpp b/src/VersionConstants.hpp index 34acb00..d01c73f 100644 --- a/src/VersionConstants.hpp +++ b/src/VersionConstants.hpp @@ -28,11 +28,7 @@ namespace VersionConstants { VersionNumber header_to_version_number(const std::string& header); constexpr int SAVEFILE_HEADER_LEN = 12; // DO NOT CHANGE THIS HEADER LENGTH - const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000009"; // Change whenever the save file is incompatible with the previous version - const std::string CURRENT_VERSION_STRING = "0.9.0-fork"; - constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 9, 0); - /** Short git SHA baked at configure time (`BuildCommit.hpp`). */ - const std::string& build_commit_short(); - /** e.g. `0.9.0-fork+7f34283` */ - std::string full_version_string(); + const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000008"; // Change whenever the save file is incompatible with the previous version + const std::string CURRENT_VERSION_STRING = "0.8.0-fork"; + constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 8, 0); } diff --git a/src/WindowsPortableUpdater.cpp b/src/WindowsPortableUpdater.cpp deleted file mode 100644 index ac4ea01..0000000 --- a/src/WindowsPortableUpdater.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "WindowsPortableUpdater.hpp" - -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#endif - -namespace WindowsPortableUpdater { - -bool parse_continuous_release_json( - const std::string& jsonText, - std::string& outLabel, - std::string& outCommit, - std::string& outZipUrl) -{ - outLabel.clear(); - outCommit.clear(); - outZipUrl.clear(); - try { - auto j = nlohmann::json::parse(jsonText); - outLabel = j.value("name", ""); - std::regex shaRe(R"(\+([0-9a-fA-F]{7,40})\b)"); - std::smatch m; - if(std::regex_search(outLabel, m, shaRe)) - outCommit = m[1].str().substr(0, 7); - if(outCommit.empty()) { - std::string body = j.value("body", ""); - if(std::regex_search(body, m, shaRe)) - outCommit = m[1].str().substr(0, 7); - } - if(!j.contains("assets") || !j["assets"].is_array()) - return false; - // Prefer the zip that matches the release commit (rolling tag keeps older assets). - auto pick_url = [](const nlohmann::json& a) -> std::string { - std::string u = a.value("browser_download_url", ""); - if(u.empty()) - u = a.value("url", ""); - return u; - }; - for(auto& a : j["assets"]) { - std::string name = a.value("name", ""); - if(name.find("win-x86_64-portable.zip") == std::string::npos) - continue; - if(!outCommit.empty() && name.find(outCommit) == std::string::npos) - continue; - if(outCommit.empty() && std::regex_search(name, m, shaRe)) - outCommit = m[1].str().substr(0, 7); - outZipUrl = pick_url(a); - if(!outZipUrl.empty()) - break; - } - // Fallback: newest matching zip by created_at if commit filter found nothing. - if(outZipUrl.empty()) { - std::string bestCreated; - for(auto& a : j["assets"]) { - std::string name = a.value("name", ""); - if(name.find("win-x86_64-portable.zip") == std::string::npos) - continue; - std::string created = a.value("created_at", ""); - if(created < bestCreated && !bestCreated.empty()) - continue; - std::string url = pick_url(a); - if(url.empty()) - continue; - bestCreated = created; - outZipUrl = url; - if(outCommit.empty() && std::regex_search(name, m, shaRe)) - outCommit = m[1].str().substr(0, 7); - } - } - return !outCommit.empty() && !outZipUrl.empty(); - } - catch(const std::exception& e) { - Logger::get().log(Logger::LogType::INFO, std::string("Windows update JSON parse failed: ") + e.what()); - return false; - } -} - -bool apply_zip_and_restart(const std::string& zipBytes) { -#ifdef _WIN32 - namespace fs = std::filesystem; - try { - const char* basePath = SDL_GetBasePath(); - if(!basePath) - return false; - fs::path appDir = fs::path(basePath); - SDL_free(const_cast(basePath)); - - fs::path tempRoot = fs::temp_directory_path() / "infinipaint-update"; - fs::create_directories(tempRoot); - fs::path zipPath = tempRoot / "update.zip"; - fs::path extractDir = tempRoot / "extracted"; - fs::path batPath = tempRoot / "apply_update.bat"; - - { - std::ofstream out(zipPath, std::ios::binary); - if(!out) - return false; - out.write(zipBytes.data(), static_cast(zipBytes.size())); - } - - DWORD pid = GetCurrentProcessId(); - std::ofstream bat(batPath); - if(!bat) - return false; - bat << "@echo off\r\n" - << "setlocal\r\n" - << "set \"APPDIR=" << appDir.string() << "\"\r\n" - << "set \"ZIP=" << zipPath.string() << "\"\r\n" - << "set \"OUT=" << extractDir.string() << "\"\r\n" - << "set \"PID=" << pid << "\"\r\n" - << ":wait\r\n" - << "tasklist /FI \"PID eq %PID%\" 2>nul | find \"%PID%\" >nul\r\n" - << "if not errorlevel 1 (\r\n" - << " timeout /t 1 /nobreak >nul\r\n" - << " goto wait\r\n" - << ")\r\n" - << "if exist \"%OUT%\" rmdir /s /q \"%OUT%\"\r\n" - << "mkdir \"%OUT%\" >nul 2>nul\r\n" - << "powershell -NoProfile -ExecutionPolicy Bypass -Command " - << "\"Expand-Archive -Force -Path '%ZIP%' -DestinationPath '%OUT%'\"\r\n" - << "if errorlevel 1 exit /b 1\r\n" - << "xcopy /E /Y /Q \"%OUT%\\*\" \"%APPDIR%\\\" >nul\r\n" - << "start \"\" \"%APPDIR%\\infinipaint.exe\"\r\n" - << "endlocal\r\n"; - bat.close(); - - std::string batStr = batPath.string(); - HINSTANCE hi = ShellExecuteA(nullptr, "open", batStr.c_str(), nullptr, nullptr, SW_HIDE); - if(reinterpret_cast(hi) <= 32) { - Logger::get().log(Logger::LogType::INFO, "ShellExecute apply_update.bat failed"); - return false; - } - Logger::get().log(Logger::LogType::INFO, "Windows update script launched; quitting to apply"); - SDL_Event e{}; - e.type = SDL_EVENT_QUIT; - SDL_PushEvent(&e); - return true; - } - catch(const std::exception& e) { - Logger::get().log(Logger::LogType::INFO, std::string("Windows update apply failed: ") + e.what()); - return false; - } -#else - (void)zipBytes; - return false; -#endif -} - -} diff --git a/src/WindowsPortableUpdater.hpp b/src/WindowsPortableUpdater.hpp deleted file mode 100644 index 5464387..0000000 --- a/src/WindowsPortableUpdater.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -/** Windows portable self-update helpers (Forgejo windows-continuous). */ -namespace WindowsPortableUpdater { - /** Parse Forgejo release JSON; fill remote label, commit, zip URL. Returns true if usable. */ - bool parse_continuous_release_json( - const std::string& jsonText, - std::string& outLabel, - std::string& outCommit, - std::string& outZipUrl); - - /** Write zip + apply script, then request process exit. Returns false on I/O failure. */ - bool apply_zip_and_restart(const std::string& zipBytes); -} diff --git a/src/World.cpp b/src/World.cpp index 0a23226..3cecd46 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -70,7 +70,6 @@ World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEvent saveThumbnail = worldInfo.saveThumbnail; init_net_obj_type_list(); - drawProg.ensure_collab_defaults(); netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) { undo.remove_by_netid(idToDestroy); }); @@ -136,7 +135,6 @@ void World::init_net_obj_type_list() { NetworkingObjects::register_ordered_list_class(netObjMan); DrawingProgramLayerListItem::register_class(*this); NetworkingObjects::register_ordered_list_class(netObjMan); - DrawingProgram::register_collab_defaults_class(*this); canvasTheme.register_class(); ClientData::register_class(*this); NetworkingObjects::register_unordered_set_class(netObjMan); @@ -435,7 +433,6 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s newClientData.camCoords = ownClientData->get_cam_coords(); newClientData.windowSize = ownClientData->get_window_size(); newClientData.gridSize = ownClientData->get_grid_size(); - newClientData.personDefaults = drawProg.collab_defaults().newPlayerDefaults; NetworkingObjects::NetObjTemporaryPtr clientDataObjPtr = clients->emplace_direct(clients, newClientData); client->customID = clientDataObjPtr.get_net_id().data;