commit f683e4178cf551e017f652ddc0957328f43fde04 Author: Anatolii Hrynchuk Date: Tue Oct 27 22:14:06 2026 +0200 Implement native Windows KSwitcher with configurable hotkeys and clipboard history diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1217798 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Microsoft +IndentWidth: 4 +ColumnLimit: 110 +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +BreakBeforeBraces: Allman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52d7d11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/build/ +/.tools/ +/data/ +/.vs/ +/.idea/ +/dist/ +/build-output.txt +*.user diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ef7ce6c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.24) +project(KSwitcher VERSION 0.1.0 LANGUAGES CXX RC) +if(NOT WIN32) + message(FATAL_ERROR "KSwitcher uses native Windows APIs") +endif() +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_library(kscore src/core.cpp src/platform.cpp src/autostart.cpp src/storage.cpp src/ocr.cpp src/richtext.cpp) +target_include_directories(kscore PUBLIC src PRIVATE vendor) +target_compile_definitions(kscore PUBLIC UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0A00) +target_link_libraries(kscore PUBLIC user32 shell32 advapi32 crypt32 imm32 comctl32 gdi32 ole32 oleaut32 taskschd comdlg32 uuid windowscodecs runtimeobject) +add_executable(KSwitcher WIN32 src/main.cpp src/app.cpp src/ui.cpp resources/app.rc) +target_link_libraries(KSwitcher PRIVATE kscore) +add_executable(KSwitcherTests tests/tests.cpp) +target_link_libraries(KSwitcherTests PRIVATE kscore) +add_executable(KSwitcherIntegration tests/integration.cpp src/app.cpp src/ui.cpp resources/app.rc) +target_link_libraries(KSwitcherIntegration PRIVATE kscore) +add_executable(KSwitcherOcrTests tests/ocr.cpp) +target_link_libraries(KSwitcherOcrTests PRIVATE kscore) +if(MSVC) + set_property(TARGET kscore KSwitcher KSwitcherTests KSwitcherIntegration KSwitcherOcrTests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + target_compile_options(kscore PRIVATE /W4 /utf-8) + target_compile_options(KSwitcher PRIVATE /W4 /utf-8) +else() + target_link_options(KSwitcher PRIVATE -municode -static -static-libgcc -static-libstdc++) + target_link_options(KSwitcherTests PRIVATE -static -static-libgcc -static-libstdc++) + target_link_options(KSwitcherIntegration PRIVATE -static -static-libgcc -static-libstdc++) + target_link_options(KSwitcherOcrTests PRIVATE -static -static-libgcc -static-libstdc++) +endif() +enable_testing() +add_test(NAME native-regressions COMMAND KSwitcherTests) +install(TARGETS KSwitcher RUNTIME DESTINATION .) +install(FILES LICENSE vendor/json-LICENSE.txt DESTINATION .) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2dc0ca --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 bitcodepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b42e30 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# KSwitcher + +Native C++20 / Win32 rewrite of the Windows functionality of Typlo. Windows 10/11 x64. +No .NET, Avalonia, WebView, Qt, external graphics engine, or separately installed C++ runtime. +The original Typlo project and its data remain separate. + +## Run + +Run `build/KSwitcher.exe`, or extract `dist/KSwitcher-win-x64.zip` and run the executable. +The build also refreshes the release copy in `build/release/`. Exit KSwitcher before rebuilding. +The application lives in the notification area. Right-click its icon for Settings, Snippets and history. +Double-click the tray icon to open Settings, the bold default menu item. +In Settings, enable **Start with Windows** and **Start with Windows as administrator** +to use a per-user Task Scheduler logon task with highest privileges. Enabling or changing +the elevated task requests UAC approval if the app is not already elevated. No password is stored; +the task runs in the signed-in user's interactive session, including on battery power. +Disabling the administrator option restores ordinary per-user startup; disabling startup stops +future automatic launches. Saving unrelated settings does not recreate startup entries. +`KSwitcher.exe --settings` opens Settings, including when the app is already running. +Exit Typlo when using overlapping shortcuts to avoid competing keyboard hooks. + +### Change a shortcut + +1. Open **Settings** and click a shortcut field. +2. Press the desired combination. The field records the actual keys; no typing of names is needed. +3. Click **Save**. Duplicate shortcuts within KSwitcher and protected Windows combinations are reported. + +This also works for each layout's **Direct layout shortcut**. Backspace or Delete without modifiers +clears a shortcut. Tab moves to the next field. The recorded shortcut is suppressed while the field +has focus, so recording the conversion shortcut does not perform a conversion. +All configured shortcuts use the existing keyboard hook and can replace ordinary Windows/app +shortcuts, including **Win+V** and **Win+Shift+S**, while KSwitcher is running. Only exact configured +combinations are intercepted. Unassigned shortcuts and simulated copy/paste pass through. +Choose another shortcut or exit KSwitcher to restore the original action. Windows protects +Ctrl+Alt+Delete and Win+L; these cannot be assigned. + +## Implemented + +- Installed Windows keyboard layouts, configurable cycle/order and direct shortcuts; IMEs are switch-only. +- Selection conversion and verified recent input (physical scan codes, 2-second typing-run boundary). +- Same-process clipboard ownership plus verified WebView2 descendants, including the Teams fix. +- Teams recent-word conversion uses a single word-selection command and validates the copied range. + If the editor's word boundary differs from the recorded typing, it falls back to paced character + selection: Teams can coalesce a burst of Shift+Left events into just one character. +- Clipboard snapshot/restoration with concurrent-copy, foreground and input-version checks. +- Case cycling, Russian/Ukrainian transliteration, verified undo and repeated conversion. +- Text snippets across layouts, multiline insertion and a verified `{cursor}` position; + `{{cursor}}` inserts the literal marker. Snippets are stored as plaintext settings, as in Typlo. +- Clipboard history for text, HTML/RTF, file lists, DIB and PNG images; plain-text paste; + full-text search, pinning, deletion, full text viewing and image previews. +- Image thumbnails and text/file icons appear directly in the history list. Only visible rows + load thumbnails; the 32-entry thumbnail cache is bounded to about 3.3 MiB and released on close. +- Text rows render RTF and basic clipboard HTML emphasis (bold, italic, underline, strike-through), + paragraphs, line breaks and list bullets using native Windows RichEdit. Previews are bounded to + 4096 characters / 64 KiB RTF, with at most 32 cached text previews; original paste formats are unchanged. + HTML is parsed as text, without a browser, scripts or external resources. Complex web layouts and + stylesheet rules are not reproduced; preview text uses the list's colors for readability. +- Consecutive screenshot copies are compared by image content, ignoring changing OLE transport + metadata (including Snipping Tool's repeated clipboard publication). +- Windows OCR, entirely native and offline. Right-click an image in history and select + **Copy text from image**. Requires a Windows OCR language installed for the user profile. +- History exclusion flags and excluded processes, pause/resume, count/size limits. +- Per-user Windows DPAPI encryption for both history payloads and the preview index. +- Autostart, keep-screen-awake, idle-only F15 activity signal, settings import/export. +- Single instance and `--history`, `--convert`, `--case`, `--transliterate`, `--settings`, + `--snippets`, `--exit` commands. `--convert` cycles layouts. + +Default shortcuts match Typlo: Ctrl+Alt+Shift+Space (cycle), C (case), T (transliteration), +Z (undo), 1/2/3 (first layouts), and Ctrl+Shift+V (history). + +History controls: Enter to paste, Shift+Enter for plain text, Ctrl+1…9 for quick paste, +Ctrl+P to pin/unpin, Delete to delete, Ctrl+O for the full item, Escape to hide. +Pressing the history shortcut a second time pastes the second-most-recent item. + +## Data and migration + +Data is in `data/` beside the executable, falling back to `%LOCALAPPDATA%/KSwitcher` when +the executable's directory is not writable. Each clipboard payload is an encrypted disk file; +only bounded previews/metadata stay in memory. Image data is loaded on demand and released when +the history popup is hidden. The log contains operational messages, never copied text or keystrokes. + +Settings **Import** accepts a Typlo `settings.json`, preserving supported Windows layout IDs, +hotkeys, snippets, exclusions and limits. Import is staged until Save. Existing Typlo files are +never modified. Typlo's encrypted SQLite history is **not migrated** to the new DPAPI file format. +DPAPI data belongs to the Windows account/machine; copying the portable folder is not a cross-account backup. + +## Differences still to port + +This is a working native implementation, not a claim of complete UI/release parity: + +- UI currently uses English and the native Windows control appearance. Typlo's ten-language UI and + explicit light/dark theme selector are not yet ported. +- Automatic release download/replacement is not enabled. KSwitcher has no configured release feed; + using Typlo's feed would install the wrong application. Updates are manual. +- No import of old encrypted clipboard history. Export/import applies to settings. +- The clipboard popup is fixed-size with per-monitor DPI scaling; PNG/DIB previews use Windows WIC/GDI. + +The Windows focus/injection rules still apply: an unelevated process cannot edit an elevated app. +Conversion is cancelled rather than pasting into a different window or replacing unverified recent text. + +## Build + +The easiest reproducible local route uses the pinned portable w64devkit toolchain: + +```powershell +.\setup-toolchain.ps1 +.\build.ps1 -Test +.\package.ps1 +``` + +`setup-toolchain.ps1` downloads the compiler from its upstream GitHub release and verifies SHA-256. +`.tools/`, build objects, test executables and compiler headers are development-only. **Do not distribute +the entire source/build folder.** The package contains just the application and notices/documentation. +The vendored JSON header is nlohmann/json 3.12.0 (MIT); it is compiled into the executable. + +Alternatively open this directory as a CMake project in Visual Studio with the C++ desktop workload +and Windows SDK installed, or use CMake with a Windows GCC toolchain. CMake/MSVC is provided but the +verified local build uses GCC/w64devkit. + +## Verification + +```powershell +.\build.ps1 -Test # Pure/native regression checks; does not touch the user's clipboard +.\build.ps1 -Ocr # Render a synthetic image, decode it with WIC and recognize it with Windows OCR +.\build.ps1 -Integration # Interactive, isolated test editor; avoid input for about 10 seconds +``` + +The interactive test requires installed English-US and Russian layouts. It opens its own editor, +tests real copy/paste, undo, repeat conversion, recent-input verification and snippet cursor placement, +then restores the clipboard. Test data is stored in its own temporary directory. It never sends messages +to Teams or other user applications. Do not run it while typing elsewhere. + +`KSwitcher.exe --smoke-test` starts a normal tray instance and exits after ten seconds for idle-memory checks. +It still uses the normal instance lock; close a running KSwitcher first if measuring a fresh process. + +## Structure + +- `core.*`: conversion, transliteration, snippets, shortcut parsing/formatting, recent-input model. +- `platform.*`: Win32 layouts, clipboard, input, ownership, DPAPI and file primitives. +- `storage.*`: settings and encrypted, disk-backed history. +- `app.*`: message loop, hooks, background work and guarded text transactions. +- `ui.cpp`: native windows and shortcut recording. +- `ocr.*`: Windows WIC and OCR via the native Windows Runtime ABI. +- `tests/`: regression checks, real editor integration and OCR fixture. + +MIT licensed; preserves the original Typlo attribution in `LICENSE`. diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..64c8882 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,46 @@ +param([switch]$Test, [switch]$Integration, [switch]$Ocr, [switch]$Stage) +$ErrorActionPreference = 'Stop' +$root = $PSScriptRoot +$compiler = Join-Path $root '.tools\w64devkit\bin\g++.exe' +if (!(Test-Path $compiler)) { + $compiler = (Get-Command g++ -ErrorAction Stop).Source +} +$env:PATH = (Split-Path $compiler) + ';' + $env:PATH +$outDir = Join-Path $root 'build' +New-Item -ItemType Directory -Force $outDir | Out-Null +$executablePath = if ($Stage) { Join-Path $outDir 'staged\KSwitcher.exe' } else { Join-Path $outDir 'KSwitcher.exe' } +New-Item -ItemType Directory -Force (Split-Path $executablePath) | Out-Null +Push-Location $root +try { + $common = @('-std=c++20','-Os','-DNDEBUG','-DUNICODE','-D_UNICODE','-DWIN32_LEAN_AND_MEAN','-DNOMINMAX','-D_WIN32_WINNT=0x0A00','-Wall','-Wextra','-Wno-missing-field-initializers','-Isrc','-Ivendor','-ffunction-sections','-fdata-sections') + $libs = @('-luser32','-lshell32','-ladvapi32','-lcrypt32','-limm32','-lcomctl32','-lgdi32','-lole32','-loleaut32','-ltaskschd','-lcomdlg32','-luuid','-lwindowscodecs','-lruntimeobject') + foreach ($name in @('core','platform','autostart','storage','ocr','richtext','app','ui','main')) { + & $compiler @common -c "src/$name.cpp" -o "build/$name.o" + if ($LASTEXITCODE) { throw "Compilation failed: $name" } + } + & windres resources/app.rc -O coff -o build/app-res.o + if ($LASTEXITCODE) { throw 'Resource compilation failed' } + New-Item -ItemType Directory -Force build/release | Out-Null + & $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/autostart.o build/storage.o build/ocr.o build/richtext.o build/app.o build/ui.o build/main.o build/app-res.o @libs -o $executablePath + if ($LASTEXITCODE) { throw 'Link failed' } + Copy-Item -LiteralPath $executablePath -Destination build/release/KSwitcher.exe -Force + if ($Test) { + & $compiler @common tests/tests.cpp build/core.o build/platform.o build/autostart.o build/storage.o build/ocr.o build/richtext.o @libs -static -s -o build/KSwitcherTests.exe + if ($LASTEXITCODE) { throw 'Test build failed' } + & .\build\KSwitcherTests.exe + if ($LASTEXITCODE) { throw 'Tests failed' } + } + if ($Integration) { + & $compiler @common tests/integration.cpp build/core.o build/platform.o build/autostart.o build/storage.o build/ocr.o build/richtext.o build/app.o build/ui.o build/app-res.o @libs -static -s -o build/KSwitcherIntegration.exe + if ($LASTEXITCODE) { throw 'Integration test build failed' } + & .\build\KSwitcherIntegration.exe + if ($LASTEXITCODE) { throw 'Integration tests failed' } + } + if ($Ocr) { + & $compiler @common tests/ocr.cpp build/core.o build/platform.o build/ocr.o @libs -static -s -o build/KSwitcherOcrTests.exe + if ($LASTEXITCODE) { throw 'OCR test build failed' } + & .\build\KSwitcherOcrTests.exe + if ($LASTEXITCODE) { throw 'OCR tests failed' } + } + Get-Item build/release/KSwitcher.exe | Select-Object FullName,Length +} finally { Pop-Location } diff --git a/package.ps1 b/package.ps1 new file mode 100644 index 0000000..fb4c158 --- /dev/null +++ b/package.ps1 @@ -0,0 +1,13 @@ +$ErrorActionPreference = 'Stop' +$root = $PSScriptRoot +$destination = Join-Path $root 'dist\KSwitcher' +New-Item -ItemType Directory -Force $destination | Out-Null +Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.exe') -Destination $destination +Copy-Item -LiteralPath (Join-Path $root 'LICENSE') -Destination $destination +Copy-Item -LiteralPath (Join-Path $root 'vendor\json-LICENSE.txt') -Destination $destination +Copy-Item -LiteralPath (Join-Path $root 'README.md') -Destination $destination +# Enumerate the distributable files explicitly; do not include a user's data directory. +$files = @('KSwitcher.exe','LICENSE','json-LICENSE.txt','README.md') | ForEach-Object { Join-Path $destination $_ } +$archive = Join-Path $root 'dist\KSwitcher-win-x64.zip' +Compress-Archive -LiteralPath $files -DestinationPath $archive -Force +Get-FileHash -LiteralPath $archive -Algorithm SHA256 diff --git a/resources/app.manifest b/resources/app.manifest new file mode 100644 index 0000000..9fdea2d --- /dev/null +++ b/resources/app.manifest @@ -0,0 +1,9 @@ + + + + KSwitcher native keyboard and clipboard utility + + + true/pmPerMonitorV2 + + diff --git a/resources/app.rc b/resources/app.rc new file mode 100644 index 0000000..78a816c --- /dev/null +++ b/resources/app.rc @@ -0,0 +1,24 @@ +#include +#include "resource.h" +IDI_KSWITCHER ICON "kswitcher.ico" +1 RT_MANIFEST "app.manifest" +1 VERSIONINFO + FILEVERSION 0,1,0,0 + PRODUCTVERSION 0,1,0,0 + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility" + VALUE "ProductName", "KSwitcher" + VALUE "FileVersion", "0.1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/resources/kswitcher.ico b/resources/kswitcher.ico new file mode 100644 index 0000000..9616340 Binary files /dev/null and b/resources/kswitcher.ico differ diff --git a/resources/kswitcher.png b/resources/kswitcher.png new file mode 100644 index 0000000..cf9c521 Binary files /dev/null and b/resources/kswitcher.png differ diff --git a/resources/kswitcher.svg b/resources/kswitcher.svg new file mode 100644 index 0000000..0034f93 --- /dev/null +++ b/resources/kswitcher.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/resource.h b/resources/resource.h new file mode 100644 index 0000000..5618279 --- /dev/null +++ b/resources/resource.h @@ -0,0 +1,2 @@ +#pragma once +#define IDI_KSWITCHER 101 diff --git a/setup-toolchain.ps1 b/setup-toolchain.ps1 new file mode 100644 index 0000000..c773c6b --- /dev/null +++ b/setup-toolchain.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = 'Stop' +$tools = Join-Path $PSScriptRoot '.tools' +New-Item -ItemType Directory -Force $tools | Out-Null +$archive = Join-Path $tools 'w64devkit.exe' +$uri = 'https://github.com/skeeto/w64devkit/releases/download/v2.10.1/w64devkit-x64-2.10.1.7z.exe' +$expected = 'ADD1EDF984CE4D473CFEB7C52E0AB38DB73A855BBA6C7F564D5E16648EB2896A' +if (!(Test-Path $archive)) { Invoke-WebRequest $uri -OutFile $archive } +if ((Get-FileHash $archive -Algorithm SHA256).Hash -ne $expected) { throw 'Toolchain checksum mismatch' } +& $archive -y "-o$tools" +if ($LASTEXITCODE) { throw 'Toolchain extraction failed' } +Write-Host 'Compiler ready. Run .\build.ps1 -Test' diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..02b87c7 --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,1139 @@ +#include "app.h" +#include "../resources/resource.h" +#include "ocr.h" +#include +#include +#include + +namespace ks +{ +App *App::current = nullptr; +namespace +{ +std::vector> shortcuts(const Settings &s) +{ + std::vector> out{{Toggle, s.toggle}, + {Case, s.casing}, + {Translit, s.translit}, + {Undo, s.undo}, + {ShowHistory, s.clipboard}}; + for (size_t i = 0; i < s.direct.size(); ++i) + out.emplace_back(Direct + (int)i, s.direct[i]); + return out; +} +size_t caretSteps(std::wstring text) +{ + size_t n = 0; + for (size_t i = 0; i < text.size(); ++i) + { + if (text[i] == L'\r' && i + 1 < text.size() && text[i + 1] == L'\n') + ++i; + else if (text[i] >= 0xd800 && text[i] <= 0xdbff && i + 1 < text.size() && text[i + 1] >= 0xdc00 && + text[i + 1] <= 0xdfff) + ++i; + ++n; + } + return n; +} +std::wstring newlines(std::wstring text) +{ + std::wstring out; + for (size_t i = 0; i < text.size(); ++i) + { + if (text[i] == L'\r' && i + 1 < text.size() && text[i + 1] == L'\n') + continue; + out += text[i]; + } + return out; +} +// Every transaction checks input, foreground and clipboard ownership before replacing text. +class Transaction +{ + App &app; + HWND target; + uint64_t version; + Clip snapshot; + DWORD expected{}; + bool captured{}; + std::wstring adopted; + + public: + Transaction(App &a, HWND t, uint64_t v) : app(a), target(t), version(v) + { + app.suspended = true; + try + { + expected = GetClipboardSequenceNumber(); + snapshot = readClipboard(app.window, 64 * 1024 * 1024); + if (expected != GetClipboardSequenceNumber()) + throw std::runtime_error("Clipboard changed while taking snapshot"); + captured = true; + } + catch (...) + { + app.suspended = false; + throw; + } + } + ~Transaction() + { + if (captured && GetClipboardSequenceNumber() == expected) + try + { + app.ownSequence = writeClipboard(app.window, snapshot); + } + catch (...) + { + app.report(L"Could not restore clipboard; the copied text remains available."); + } + app.suspended = false; + } + bool valid() const + { + return target && IsWindow(target) && GetForegroundWindow() == target && + app.inputVersion.load() == version; + } + void check() + { + if (GetClipboardSequenceNumber() != expected) + { + auto now = GetClipboardSequenceNumber(); + if (adopted.empty() || !ownsClipboard(target) || readClipboard(app.window).text() != adopted || + GetClipboardSequenceNumber() != now) + throw std::runtime_error("Operation cancelled: another application changed the clipboard"); + expected = now; + } + } + void write(const Clip &c) + { + check(); + adopted.clear(); + app.ownSequence = expected = writeClipboard(app.window, c); + // A concurrent writer can win immediately after CloseClipboard, before we read its sequence. + // Never adopt that writer's sequence as our own and paste its content. + if (GetClipboardOwner() != app.window) + throw std::runtime_error("Clipboard changed immediately after writing"); + auto text = c.text(); + if ((!text.empty() && readClipboard(app.window).text() != text) || + GetClipboardSequenceNumber() != expected) + throw std::runtime_error("Clipboard changed while verifying our write"); + } + std::wstring copy() + { + if (!valid()) + return {}; + write(textClip(L"\x2063KSwitcher-" + std::to_wstring(GetTickCount64()))); + DWORD before = expected; + if (!valid() || !chord(L'C', MOD_CONTROL)) + return {}; + ULONGLONG deadline = GetTickCount64() + 2000; + DWORD changed = before; + ULONGLONG stable = 0; + bool changedAgain = false; + while (GetTickCount64() < deadline) + { + if (!valid()) + return {}; + DWORD now = GetClipboardSequenceNumber(); + if (now != changed) + { + changedAgain |= changed != before; + changed = now; + stable = GetTickCount64(); + } + if (changed != before && GetTickCount64() - stable >= 60) + break; + Sleep(10); + } + if (changed == before || !valid()) + return {}; + if ((GetClipboardOwner() ? !ownsClipboard(target) : changedAgain) || + changed != GetClipboardSequenceNumber()) + throw std::runtime_error("Operation cancelled: copy did not belong to the target application"); + expected = changed; + auto clip = readClipboard(app.window); + auto text = clip.text(); + adopted = text; + check(); + for (auto &f : clip.formats) + { + if (f.name == L"VisualStudioEditorOperationsLineCutCopyClipboardTag" || + f.name == L"MSDEVLineSelect") + return {}; + if (f.name == L"vscode-editor-data") + { + std::string json(f.data.begin(), f.data.end()); + if (json.find("\"isFromEmptySelection\":true") != json.npos || + json.find("\"isFromEmptySelection\": true") != json.npos) + return {}; + } + } + return text; + } + bool select(size_t n) + { + return selectPrevious(n, [&] { return valid(); }); + } + std::wstring copyRange(size_t count, const std::wstring &wordCandidate, + const std::function &matches) + { + // WebView word boundaries are only candidates; exact verification is mandatory. + if (needsSelectionDelay(target) && !wordCandidate.empty() && + std::all_of(wordCandidate.begin(), wordCandidate.end(), + [](wchar_t c) { return letter(c) || iswdigit(c); })) + { + if (!valid() || !chord(VK_LEFT, MOD_CONTROL | MOD_SHIFT)) + return {}; + Sleep(25); + auto text = copy(); + if (valid() && matches(text)) + { + log(L"Selection: verified word boundary"); + return text; + } + if (!valid() || !chord(VK_RIGHT)) + return {}; + Sleep(25); + } + return select(count) ? copy() : std::wstring{}; + } + bool paste(const Clip &clip) + { + if (!valid()) + return false; + write(clip); + for (int i = 0; i < 2; ++i) + { + check(); + if (!valid()) + return false; + } + if (!chord(L'V', MOD_CONTROL)) + throw std::runtime_error("Windows blocked simulated input; check application privilege levels"); + Sleep(300); + return true; + } +}; +} // namespace +App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot) : instance(h), smoke(test) +{ + current = this; + data = dataRoot.empty() ? dataDirectory() : dataRoot; + std::filesystem::create_directories(data); + layouts = readLayouts(); + try + { + settings = loadSettings(data / L"settings.json", layouts); + } + catch (const std::exception &e) + { + settings = loadSettings(data / L"missing-defaults.json", layouts); + registrationWarning = L"Settings were not loaded: " + wide(e.what()); + } + try + { + history = std::make_unique(data / L"history"); + } + catch (const std::exception &e) + { + registrationWarning += L"\nHistory is unavailable: " + wide(e.what()); + settings.history = false; + } +} +App::~App() +{ + if (keyboardHook) + UnhookWindowsHookEx(keyboardHook); + if (mouseHook) + UnhookWindowsHookEx(mouseHook); + { + std::lock_guard lock(jobsMutex); + stop = true; + jobs.clear(); + } + jobsReady.notify_all(); + if (worker.joinable()) + worker.join(); + RemoveClipboardFormatListener(window); + NOTIFYICONDATAW icon{sizeof(icon)}; + icon.hWnd = window; + icon.uID = 1; + Shell_NotifyIconW(NIM_DELETE, &icon); + SetThreadExecutionState(ES_CONTINUOUS); + current = nullptr; +} +void App::enqueue(std::function job) +{ + { + std::lock_guard lock(jobsMutex); + if (stop) + return; + jobs.push_back(std::move(job)); + } + jobsReady.notify_one(); +} +void App::report(const std::wstring &text) +{ + log(text); + PostMessageW(window, ErrorMessage, 0, (LPARAM) new std::wstring(text)); +} +void App::addTray() +{ + NOTIFYICONDATAW icon{sizeof(icon)}; + icon.hWnd = window; + icon.uID = 1; + icon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + icon.uCallbackMessage = TrayMessage; + icon.hIcon = (HICON)LoadImageW(instance, MAKEINTRESOURCEW(IDI_KSWITCHER), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED); + wcscpy_s(icon.szTip, L"KSwitcher — keyboard layouts & clipboard"); + Shell_NotifyIconW(NIM_ADD, &icon); + icon.uVersion = NOTIFYICON_VERSION_4; + Shell_NotifyIconW(NIM_SETVERSION, &icon); +} +bool App::registerHotkeys(const Settings &s, bool rollback) +{ + std::vector> bindings; + for (auto &[id, text] : shortcuts(s)) + { + auto key = Hotkey::parse(text); + if (key && key->key) + bindings.emplace_back(id, *key); + } + if (!bindings.empty() && !keyboardHook) + { + if (!rollback) + registrationWarning = L"Keyboard interception could not start."; + return false; + } + hookBindings = std::move(bindings); + return true; +} + +void App::applySettings(const Settings &s) +{ + bool autostartChanged = false; + validateSettings(s); + if (!registerHotkeys(s)) + { + registerHotkeys(settings, true); + throw std::runtime_error(utf8(registrationWarning)); + } + try + { + if (s.autostart != settings.autostart || s.elevatedAutostart != settings.elevatedAutostart) + { + setAutostart(s.autostart, s.elevatedAutostart); + autostartChanged = true; + } + saveSettings(data / L"settings.json", s); + } + catch (...) + { + try + { + if (autostartChanged) + setAutostart(settings.autostart, settings.elevatedAutostart); + } + catch (...) + { + } + registerHotkeys(settings, true); + throw; + } + { + std::lock_guard lock(stateMutex); + settings = s; + run.reset(); + last.reset(); + ++inputVersion; + } + SetThreadExecutionState(ES_CONTINUOUS | + (settings.awake ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)); +} +void App::reloadLayouts() +{ + auto latest = readLayouts(); + Settings s = settings; + std::vector direct(latest.size()); + for (size_t i = 0; i < latest.size(); ++i) + for (size_t j = 0; j < layouts.size(); ++j) + if (latest[i].id == layouts[j].id && j < s.direct.size()) + direct[i] = s.direct[j]; + s.direct = direct; + s.directIds.clear(); + for (auto &layout : latest) + s.directIds.push_back(layout.id); + { + std::lock_guard lock(stateMutex); + layouts = std::move(latest); + settings = s; + run.reset(); + last.reset(); + ++inputVersion; + } + registerHotkeys(settings); +} +bool App::isHotkey(DWORD key, UINT mods) +{ + for (auto &[id, text] : shortcuts(settings)) + { + auto h = Hotkey::parse(text); + if (h && h->key == key && h->modifiers == mods) + return true; + } + return false; +} +void App::resetInput() +{ + std::lock_guard lock(stateMutex); + ++inputVersion; + run.reset(); + last.reset(); +} +void App::keyboard(const KBDLLHOOKSTRUCT &k) +{ + if (k.flags & LLKHF_INJECTED) + return; + DWORD vk = k.vkCode; + if (vk == VK_LSHIFT || vk == VK_RSHIFT || vk == VK_LCONTROL || vk == VK_RCONTROL || vk == VK_LMENU || + vk == VK_RMENU || vk == VK_LWIN || vk == VK_RWIN) + return; + UINT mods = 0; + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + mods |= MOD_CONTROL; + if (GetAsyncKeyState(VK_MENU) & 0x8000) + mods |= MOD_ALT; + if (GetAsyncKeyState(VK_SHIFT) & 0x8000) + mods |= MOD_SHIFT; + if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000) + mods |= MOD_WIN; + std::lock_guard lock(stateMutex); + if (isHotkey(vk, mods)) + return; + ++inputVersion; + last.reset(); + HWND fg = GetForegroundWindow(); + HKL layout = windowLayout(fg); + if (mods & (MOD_CONTROL | MOD_ALT | MOD_WIN)) + { + run.reset(); + return; + } + if (vk == VK_BACK) + { + run.backspace(fg, GetTickCount64()); + return; + } + bool shift = (mods & MOD_SHIFT) != 0; + wchar_t ch = vk == VK_SPACE ? L' ' : physicalKey(k.scanCode, shift); + if (ch && letter(ch) && (GetKeyState(VK_CAPITAL) & 1)) + ch = shift ? lower(std::wstring(1, ch))[0] : upper(std::wstring(1, ch))[0]; + if (!ch) + { + run.reset(); + return; + } + run.record(ch, fg, layout, GetTickCount64()); + if (busy || settings.snippets.empty()) + return; + auto keys = lower(run.keys); + for (size_t i = 0; i < settings.snippets.size(); ++i) + { + auto &sn = settings.snippets[i]; + std::vector variants{lower(sn.trigger)}; + for (auto &l : layouts) + { + bool valid = true; + std::wstring normalized; + for (wchar_t c : sn.trigger) + { + auto p = physicalLayout().at(l.code(c)); + if (!p) + { + valid = false; + break; + } + normalized += p; + } + if (valid) + variants.push_back(lower(normalized)); + } + for (auto &trigger : variants) + if (keys.size() >= trigger.size() && keys.ends_with(trigger)) + { + PostMessageW(window, SnippetMessage, (WPARAM)i, 0); + return; + } + } +} +LRESULT CALLBACK App::keyProc(int code, WPARAM wp, LPARAM lp) +{ + if (code == HC_ACTION && (((KBDLLHOOKSTRUCT *)lp)->flags & LLKHF_INJECTED) && + ((KBDLLHOOKSTRUCT *)lp)->vkCode == 0xE8) + return CallNextHookEx(nullptr, code, wp, lp); // Our shell-mask key is never recorded. + if (code == HC_ACTION && current && current->hotkeyCapture && + GetForegroundWindow() == GetAncestor(current->hotkeyCapture, GA_ROOT)) + { + auto &key = *(KBDLLHOOKSTRUCT *)lp; + bool modifier = key.vkCode == VK_LCONTROL || key.vkCode == VK_RCONTROL || key.vkCode == VK_LSHIFT || + key.vkCode == VK_RSHIFT || key.vkCode == VK_LMENU || key.vkCode == VK_RMENU || + key.vkCode == VK_LWIN || key.vkCode == VK_RWIN; + if (!modifier) + { + if (wp == WM_KEYUP || wp == WM_SYSKEYUP) + current->hotkeyHook.update(key.vkCode, false, 0, {}); + if (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) + { + UINT mods = 0; + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + mods |= MOD_CONTROL; + if (GetAsyncKeyState(VK_MENU) & 0x8000) + mods |= MOD_ALT; + if (GetAsyncKeyState(VK_SHIFT) & 0x8000) + mods |= MOD_SHIFT; + if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000) + mods |= MOD_WIN; + if (mods & MOD_WIN) + chord(0xE8); + PostMessageW(current->hotkeyCapture, CaptureHotkeyMessage, key.vkCode, mods); + } + return 1; // Record the chord without executing a global shortcut or typing into an editor. + } + } + if (code == HC_ACTION && (((KBDLLHOOKSTRUCT *)lp)->flags & LLKHF_INJECTED)) + return CallNextHookEx(nullptr, code, wp, lp); + if (code == HC_ACTION && (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) && current) + { + auto &key = *(KBDLLHOOKSTRUCT *)lp; + UINT mods = 0; + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + mods |= MOD_CONTROL; + if (GetAsyncKeyState(VK_MENU) & 0x8000) + mods |= MOD_ALT; + if (GetAsyncKeyState(VK_SHIFT) & 0x8000) + mods |= MOD_SHIFT; + if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000) + mods |= MOD_WIN; + auto action = current->hotkeyHook.update(key.vkCode, true, mods, current->hookBindings); + if (action > 0) + { + // Tell the shell that Win participated in a chord, preventing Start + // from opening on Win-up after we consumed V. 0xE8 is unassigned. + if (mods & (MOD_WIN | MOD_ALT)) + chord(0xE8); + PostMessageW(current->window, WM_HOTKEY, action, 0); + } + if (action != 0) + return 1; + current->keyboard(*(KBDLLHOOKSTRUCT *)lp); + } + if (code == HC_ACTION && (wp == WM_KEYUP || wp == WM_SYSKEYUP) && current && + current->hotkeyHook.update(((KBDLLHOOKSTRUCT *)lp)->vkCode, false, 0, current->hookBindings) != 0) + return 1; + return CallNextHookEx(nullptr, code, wp, lp); +} +LRESULT CALLBACK App::mouseProc(int code, WPARAM wp, LPARAM lp) +{ + if (code == HC_ACTION && current) + { + auto *e = (MSLLHOOKSTRUCT *)lp; + if (!(e->flags & LLMHF_INJECTED) && + (wp == WM_LBUTTONDOWN || wp == WM_RBUTTONDOWN || wp == WM_MBUTTONDOWN || wp == WM_MOUSEWHEEL)) + current->resetInput(); + } + return CallNextHookEx(nullptr, code, wp, lp); +} +void App::dispatch(int command) +{ + try + { + if (command == ShowHistory) + { + showHistory(*this); + return; + } + if (command == 20) + { + showSettings(*this); + return; + } + if (command == 21) + { + showSnippets(*this); + return; + } + if (command == 99) + { + PostMessageW(window, WM_CLOSE, 0, 0); + return; + } + transform(command); + } + catch (const std::exception &e) + { + report(wide(e.what())); + } +} +void App::transform(int command, int snippet) +{ + if (busy.exchange(true)) + return; + HWND target = GetForegroundWindow(); + Run recent; + std::optional previous; + Settings config; + std::vector available; + uint64_t version; + { + std::lock_guard lock(stateMutex); + recent = run; + previous = last; + config = settings; + available = layouts; + version = inputVersion; + } + enqueue( + [this, target, recent, previous, config, available, version, command, snippet] + { + struct BusyReset + { + std::atomic &b; + ~BusyReset() + { + b = false; + } + } reset{busy}; + auto start = GetTickCount64(); + try + { + auto valid = [&] + { return GetForegroundWindow() == target && inputVersion.load() == version; }; + for (int i = 0; i < 8 && !modifiersReleased(); ++i) + Sleep(10); + if (!modifiersReleased()) + releaseModifiers(); + if (!valid()) + return; + int hint = -1; + for (int i = 0; i < (int)available.size(); ++i) + if (available[i].handle == windowLayout(target)) + hint = i; + int to = command >= Direct ? command - Direct : -1; + if (to >= (int)available.size()) + return; + if (to >= 0 && !available[to].convertible) + { + PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle); + return; + } + Transaction tx(*this, target, version); + log(L"Transform target=" + processName(target) + L" command=" + std::to_wstring(command) + + L" recent=" + std::to_wstring(config.recent) + L" keys=" + + std::to_wstring(recent.keys.size()) + L" sameWindow=" + + std::to_wstring(recent.window == target)); + std::wstring text, result; + bool automatic = false; + if (command == Undo) + { + if (!previous || previous->window != target || previous->version != version || + GetTickCount64() - previous->at > 120000) + return; + text = tx.copyRange(caretSteps(previous->result), previous->result, + [&](const std::wstring &candidate) + { return newlines(candidate) == newlines(previous->result); }); + if (newlines(text) != newlines(previous->result)) + { + if (tx.valid()) + chord(VK_RIGHT); + return; + } + result = previous->original; + automatic = true; + } + else if (snippet >= 0) + { + if (snippet >= (int)config.snippets.size() || recent.window != target) + return; + const auto &sn = config.snippets[snippet]; + if (recent.keys.size() < sn.trigger.size()) + return; + Run tail = recent; + tail.keys = tail.keys.substr(tail.keys.size() - sn.trigger.size()); + if (!tx.select(sn.trigger.size())) + return; + text = tx.copy(); + if (tail.match(text, available, physicalLayout()) < 0) + { + if (tx.valid()) + chord(VK_RIGHT); + return; + } + result = parseTemplate(sn.text).text; + automatic = true; + } + else if (config.recent && recent.window == target && !recent.keys.empty() && !recent.overflow) + { + text = + tx.copyRange(recent.keys.size(), recent.keys, [&](const std::wstring &candidate) + { return recent.match(candidate, available, physicalLayout()) >= 0; }); + hint = recent.match(text, available, physicalLayout()); + log(L"Recent verification copied=" + std::to_wstring(text.size()) + L" keys=" + + std::to_wstring(recent.keys.size()) + L" match=" + std::to_wstring(hint)); + if (hint < 0) + { + if (tx.valid()) + chord(VK_RIGHT); + return; + } + automatic = true; + } + else if (config.recent && previous && previous->window == target && + previous->version == version) + { + text = tx.copyRange(caretSteps(previous->result), previous->result, + [&](const std::wstring &candidate) + { return newlines(candidate) == newlines(previous->result); }); + if (newlines(text) != newlines(previous->result)) + { + if (tx.valid()) + chord(VK_RIGHT); + return; + } + hint = previous->layout; + automatic = true; + } + else + text = tx.copy(); + if (!tx.valid()) + return; + if (text.empty()) + { + if (to >= 0 && config.switchLayout) + PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle); + return; + } + if (command != Undo && snippet < 0) + { + if (command == Case) + result = cycleCase(text); + else if (command == Translit) + result = transliterate(text); + else + { + if (command == Toggle) + { + std::vector cycle; + for (auto &id : config.cycle) + for (int i = 0; i < (int)available.size(); ++i) + if (available[i].id == id && available[i].convertible) + cycle.push_back(i); + if (cycle.size() < 2) + { + if (automatic && tx.valid()) + chord(VK_RIGHT); + return; + } + int source = detect(text, available, hint); + if (source < 0) + source = hint; + auto it = std::find(cycle.begin(), cycle.end(), source); + to = it == cycle.end() || ++it == cycle.end() ? cycle.front() : *it; + } + result = convertTo(text, available, to, hint); + } + } + bool sent = false; + log(L"Conversion source=" + std::to_wstring(hint) + L" target=" + std::to_wstring(to) + + L" changed=" + std::to_wstring(result != text)); + if (result != text) + sent = tx.paste(textClip(result)); + else if (automatic && tx.valid()) + chord(VK_RIGHT); + if (tx.valid() && (sent || automatic)) + { + std::lock_guard lock(stateMutex); + run.reset(); + if (command == Undo || snippet >= 0) + last.reset(); + else + last = LastTransform{target, text, result, to >= 0 ? to : hint, + GetTickCount64(), version}; + } + if (snippet >= 0 && sent && tx.valid()) + { + auto t = parseTemplate(config.snippets[snippet].text); + if (!t.suffix.empty() && tx.select(caretSteps(t.suffix))) + { + auto suffix = tx.copy(); + if (tx.valid()) + chord(newlines(suffix) == newlines(t.suffix) ? VK_LEFT : VK_RIGHT); + } + } + if (to >= 0 && config.switchLayout && tx.valid()) + PostMessageW(target, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)available[to].handle); + log(L"Text operation completed in " + std::to_wstring(GetTickCount64() - start) + L" ms"); + } + catch (const std::exception &e) + { + report(wide(e.what())); + } + }); +} +void App::pasteEntry(std::wstring id, bool plain) +{ + if (!history || busy.exchange(true)) + return; + HWND target = pasteTarget; + if (historyWindow) + SendMessageW(historyWindow, WM_CLOSE, 0, 0); + activateWindow(target); + auto version = inputVersion.load(); + enqueue( + [this, id, plain, target, version] + { + struct Reset + { + std::atomic &b; + ~Reset() + { + b = false; + } + } reset{busy}; + try + { + for (int i = 0; i < 20 && !modifiersReleased(); ++i) + Sleep(10); + if (!modifiersReleased() || GetForegroundWindow() != target || inputVersion != version) + return; + auto clip = history->load(id); + if (plain) + { + auto text = clip.text(); + if (text.empty()) + return; + clip = textClip(text); + } + Transaction tx(*this, target, version); + tx.paste(clip); + } + catch (const std::exception &e) + { + report(wide(e.what())); + } + }); +} +void App::captureHistory() +{ + DWORD sequence = GetClipboardSequenceNumber(); + if (suspended || sequence == ownSequence || !settings.history || !history || + GetTickCount64() < pauseUntil) + return; + Settings config = settings; + enqueue( + [this, sequence, config] + { + try + { + if (suspended || sequence != GetClipboardSequenceNumber() || sequence == ownSequence) + return; + UINT exclude = RegisterClipboardFormatW(L"ExcludeClipboardContentFromMonitorProcessing"), + ignore = RegisterClipboardFormatW(L"Clipboard Viewer Ignore"); + if (IsClipboardFormatAvailable(exclude) || IsClipboardFormatAvailable(ignore)) + return; + auto owner = GetClipboardOwner(); + auto app = processName(owner ? owner : GetForegroundWindow()); + for (auto &name : config.excluded) + if (lower(name) == lower(app)) + return; + auto clip = readClipboard(window, (size_t)config.maxImageMiB * 1024 * 1024); + if (sequence != GetClipboardSequenceNumber()) + return; + for (auto &f : clip.formats) + if (f.name == L"CanIncludeInClipboardHistory" && f.data.size() >= 4 && !f.data[0] && + !f.data[1] && !f.data[2] && !f.data[3]) + return; + history->add(clip, app, config); + PostMessageW(window, HistoryMessage, 0, 0); + } + catch (const std::exception &e) + { + log(L"History capture: " + wide(e.what())); + } + }); +} +void App::recognizeEntry(std::wstring id) +{ + if (!history || busy.exchange(true)) + return; + auto sequence = GetClipboardSequenceNumber(); + Settings config = settings; + enqueue( + [this, id, sequence, config] + { + struct Reset + { + std::atomic &b; + ~Reset() + { + b = false; + } + } reset{busy}; + try + { + auto text = recognizeText(history->load(id)); + if (text.empty()) + { + report(L"No text was found in the image."); + return; + } + auto clip = textClip(text); + if (config.history) + history->add(clip, L"Windows OCR", config); + if (GetClipboardSequenceNumber() == sequence) + ownSequence = writeClipboard(window, clip); + else + report(L"The clipboard changed during OCR. Recognized text is available in history."); + PostMessageW(window, HistoryMessage, 0, 0); + } + catch (const std::exception &e) + { + report(wide(e.what())); + } + }); +} +void App::trayMenu() +{ + HMENU menu = CreatePopupMenu(); + AppendMenuW(menu, MF_STRING, 10, L"Clipboard history"); + AppendMenuW(menu, MF_STRING, 11, L"Settings…"); + SetMenuDefaultItem(menu, 11, FALSE); + AppendMenuW(menu, MF_STRING, 12, L"Snippets…"); + AppendMenuW(menu, MF_STRING, 13, L"Keyboard shortcuts…"); + AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); + AppendMenuW(menu, MF_STRING | (settings.awake ? MF_CHECKED : 0), 14, L"Keep screen awake"); + HMENU pause = CreatePopupMenu(); + AppendMenuW(pause, MF_STRING, 20, L"Resume"); + AppendMenuW(pause, MF_STRING, 21, L"10 minutes"); + AppendMenuW(pause, MF_STRING, 22, L"1 hour"); + AppendMenuW(pause, MF_STRING, 23, L"Until restart"); + AppendMenuW(menu, MF_POPUP, (UINT_PTR)pause, L"Pause clipboard history"); + AppendMenuW(menu, MF_STRING, 15, L"Open data folder"); + AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); + AppendMenuW(menu, MF_STRING, 16, L"About KSwitcher"); + AppendMenuW(menu, MF_STRING, 17, L"Exit"); + POINT pt; + GetCursorPos(&pt); + pasteTarget = GetForegroundWindow(); + SetForegroundWindow(window); + int cmd = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON, pt.x, pt.y, 0, window, nullptr); + DestroyMenu(menu); + PostMessageW(window, WM_NULL, 0, 0); + try + { + switch (cmd) + { + case 10: + showHistory(*this); + break; + case 11: + showSettings(*this); + break; + case 12: + showSnippets(*this); + break; + case 13: + showShortcuts(*this); + break; + case 14: + { + auto s = settings; + s.awake = !s.awake; + applySettings(s); + break; + } + case 15: + ShellExecuteW(window, L"open", data.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + break; + case 16: + MessageBoxW(window, + L"KSwitcher 0.1.0\nNative C++ / Win32 keyboard and clipboard utility\n\nBased on " + L"Typlo by bitcodepro. MIT license.\nHistory is encrypted for your Windows " + L"account.\nNo .NET, WebView or graphics framework.", + L"About KSwitcher", MB_OK); + break; + case 17: + PostMessageW(window, WM_CLOSE, 0, 0); + break; + case 20: + pauseUntil = 0; + break; + case 21: + pauseUntil = GetTickCount64() + 600000; + break; + case 22: + pauseUntil = GetTickCount64() + 3600000; + break; + case 23: + pauseUntil = ~ULONGLONG{}; + break; + } + } + catch (const std::exception &e) + { + report(wide(e.what())); + } +} +LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + auto *app = current; + if (!app) + return DefWindowProcW(hwnd, msg, wp, lp); + if (msg == app->taskbarCreated && app->taskbarCreated) + { + app->addTray(); + return 0; + } + switch (msg) + { + case WM_HOTKEY: + app->dispatch((int)wp); + return 0; + case TrayMessage: + if (LOWORD(lp) == WM_CONTEXTMENU) + app->trayMenu(); + else if (LOWORD(lp) == WM_LBUTTONDBLCLK || LOWORD(lp) == NIN_KEYSELECT) + app->dispatch(20); // Settings is the tray's default action. + return 0; + case WM_CLIPBOARDUPDATE: + app->captureHistory(); + return 0; + case SnippetMessage: + app->transform(0, (int)wp); + return 0; + case HistoryMessage: + if (app->historyWindow && IsWindowVisible(app->historyWindow)) + refreshHistory(*app); + return 0; + case ErrorMessage: + { + std::unique_ptr text((std::wstring *)lp); + NOTIFYICONDATAW icon{sizeof(icon)}; + icon.hWnd = hwnd; + icon.uID = 1; + icon.uFlags = NIF_INFO; + wcsncpy_s(icon.szInfo, text->c_str(), _TRUNCATE); + wcscpy_s(icon.szInfoTitle, L"KSwitcher"); + icon.dwInfoFlags = NIIF_WARNING; + Shell_NotifyIconW(NIM_MODIFY, &icon); + return 0; + } + case WM_COPYDATA: + { + auto *data = (COPYDATASTRUCT *)lp; + if (data && data->dwData == 1 && data->cbData == sizeof(int)) + { + int cmd; + memcpy(&cmd, data->lpData, sizeof(cmd)); + app->dispatch(cmd); + } + return TRUE; + } + case WM_TIMER: + if (wp == 2) + { + PostMessageW(hwnd, WM_CLOSE, 0, 0); + return 0; + } + if (app->settings.awake) + { + LASTINPUTINFO idle{sizeof(idle)}; + if (GetLastInputInfo(&idle) && DWORD(GetTickCount() - idle.dwTime) >= 180000) + chord(VK_F15); + } + return 0; + case WM_CLOSE: + if (app->busy) + { + app->report(L"Wait for the current text operation to finish before exiting."); + return 0; + } + DestroyWindow(hwnd); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + default: + return DefWindowProcW(hwnd, msg, wp, lp); + } +} +int App::execute() +{ + INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES}; + InitCommonControlsEx(&controls); + WNDCLASSW cls{}; + cls.hInstance = instance; + cls.lpfnWndProc = windowProc; + cls.lpszClassName = L"KSwitcher.Main"; + cls.hIcon = LoadIconW(instance, MAKEINTRESOURCEW(IDI_KSWITCHER)); + RegisterClassW(&cls); + window = CreateWindowExW(WS_EX_TOOLWINDOW, cls.lpszClassName, L"KSwitcher", WS_POPUP, 0, 0, 0, 0, nullptr, + nullptr, instance, nullptr); + if (!window) + throw std::runtime_error("Cannot create message window"); + taskbarCreated = RegisterWindowMessageW(L"TaskbarCreated"); + worker = std::thread( + [this] + { + CoInitializeEx(nullptr, COINIT_MULTITHREADED); + for (;;) + { + std::function job; + { + std::unique_lock lock(jobsMutex); + jobsReady.wait(lock, [&] { return stop || !jobs.empty(); }); + if (stop) + break; + job = std::move(jobs.front()); + jobs.pop_front(); + } + try + { + job(); + } + catch (const std::exception &e) + { + report(wide(e.what())); + } + } + CoUninitialize(); + }); + keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, keyProc, instance, 0); + mouseHook = SetWindowsHookExW(WH_MOUSE_LL, mouseProc, instance, 0); + registerHotkeys(settings); + if (!keyboardHook || !mouseHook) + report(L"Input tracking could not start. Automatic recent-text conversion is unavailable."); + AddClipboardFormatListener(window); + addTray(); + SetTimer(window, 1, 60000, nullptr); + SetThreadExecutionState(ES_CONTINUOUS | + (settings.awake ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)); + if (!registrationWarning.empty()) + report(registrationWarning); + if (smoke) + SetTimer(window, 2, 10000, nullptr); + if (startCommand) + PostMessageW(window, WM_HOTKEY, startCommand, 0); + MSG msg; + while (GetMessageW(&msg, nullptr, 0, 0) > 0) + { + if (settingsWindow && IsDialogMessageW(settingsWindow, &msg)) + continue; + if (snippetsWindow && IsDialogMessageW(snippetsWindow, &msg)) + continue; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + return (int)msg.wParam; +} +} // namespace ks diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..8949ea9 --- /dev/null +++ b/src/app.h @@ -0,0 +1,85 @@ +#pragma once +#include "storage.h" +#include +#include +#include +#include +#include +#include + +namespace ks +{ +inline constexpr UINT TrayMessage = WM_APP + 1, ErrorMessage = WM_APP + 2, HistoryMessage = WM_APP + 3, + SnippetMessage = WM_APP + 4; +inline constexpr int Toggle = 1, Case = 2, Translit = 3, Undo = 4, ShowHistory = 5, Direct = 100; +inline constexpr UINT CaptureHotkeyMessage = WM_APP + 5; +struct LastTransform +{ + HWND window{}; + std::wstring original, result; + int layout = -1; + ULONGLONG at{}; + uint64_t version{}; +}; +class App +{ + public: + HWND window{}, historyWindow{}, settingsWindow{}, snippetsWindow{}; + HINSTANCE instance{}; + std::filesystem::path data; + std::vector layouts; + Settings settings; + std::unique_ptr history; + std::atomic busy{false}, suspended{false}; + std::atomic ownSequence{0}; + std::atomic inputVersion{0}; + std::atomic pauseUntil{0}; + HWND pasteTarget{}; + HWND hotkeyCapture{}; + std::mutex stateMutex; + Run run; + std::optional last; + std::wstring registrationWarning; + bool smoke{}; + int startCommand{}; + App(HINSTANCE, bool = false, const std::filesystem::path &dataRoot = {}); + ~App(); + int execute(); + void dispatch(int); + void transform(int, int snippet = -1); + void pasteEntry(std::wstring, bool plain); + void recognizeEntry(std::wstring); + void captureHistory(); + void enqueue(std::function); + void report(const std::wstring &); + void trayMenu(); + void addTray(); + bool registerHotkeys(const Settings &, bool rollback = false); + void applySettings(const Settings &); + void reloadLayouts(); + bool isHotkey(DWORD, UINT); + void keyboard(const KBDLLHOOKSTRUCT &); + void resetInput(); + static App *current; + + private: + HHOOK keyboardHook{}, mouseHook{}; + std::mutex jobsMutex; + std::condition_variable jobsReady; + std::deque> jobs; + std::thread worker; + bool stop{}; + UINT taskbarCreated{}; + std::vector> hookBindings; + HotkeyHook hotkeyHook; + static LRESULT CALLBACK windowProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK keyProc(int, WPARAM, LPARAM); + static LRESULT CALLBACK mouseProc(int, WPARAM, LPARAM); +}; +void showHistory(App &); +void refreshHistory(App &); +void showSettings(App &); +void showSnippets(App &); +void showShortcuts(App &); +void showEntry(App &, const std::wstring &); +} // namespace ks diff --git a/src/autostart.cpp b/src/autostart.cpp new file mode 100644 index 0000000..f89bf63 --- /dev/null +++ b/src/autostart.cpp @@ -0,0 +1,205 @@ +#include "platform.h" +#include +#include +#include +#include +namespace ks +{ +namespace +{ +template struct Com +{ + T *p{}; + ~Com() + { + if (p) + p->Release(); + } + T **out() + { + return &p; + } + T *operator->() + { + return p; + } +}; +struct Str +{ + BSTR value; + Str(const wchar_t *s) : value(SysAllocString(s)) + { + if (!value) + throw std::bad_alloc(); + } + ~Str() + { + SysFreeString(value); + } + operator BSTR() const + { + return value; + } +}; +void check(HRESULT hr) +{ + if (FAILED(hr)) + throw std::runtime_error("Cannot configure Windows autostart (HRESULT " + + std::to_string((unsigned long)hr) + ")"); +} +bool missing(HRESULT hr) +{ + return hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); +} +void runEntry(bool enabled) +{ + HKEY key{}; + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, nullptr, + 0, KEY_SET_VALUE, nullptr, &key, nullptr) != ERROR_SUCCESS) + throw std::runtime_error("Cannot access autostart settings"); + auto command = L"\"" + executable() + L"\""; + auto status = enabled ? RegSetValueExW(key, L"KSwitcher", 0, REG_SZ, (const BYTE *)command.c_str(), + DWORD((command.size() + 1) * sizeof(wchar_t))) + : RegDeleteValueW(key, L"KSwitcher"); + RegCloseKey(key); + if (status != ERROR_SUCCESS && status != ERROR_FILE_NOT_FOUND) + throw std::runtime_error("Cannot save autostart settings"); +} +} // namespace +std::wstring currentUserSid() +{ + HANDLE token{}; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + throw std::runtime_error("Cannot identify Windows account"); + DWORD size{}; + GetTokenInformation(token, TokenUser, nullptr, 0, &size); + Bytes data(size); + BOOL ok = GetTokenInformation(token, TokenUser, data.data(), size, &size); + CloseHandle(token); + if (!ok) + throw std::runtime_error("Cannot identify Windows account"); + LPWSTR sid{}; + if (!ConvertSidToStringSidW(((TOKEN_USER *)data.data())->User.Sid, &sid)) + throw std::runtime_error("Cannot read account SID"); + std::wstring result = sid; + LocalFree(sid); + return result; +} +void configureAutostart(int mode) +{ + Com service; + check(CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, + (void **)service.out())); + VARIANT empty; + VariantInit(&empty); + check(service->Connect(empty, empty, empty, empty)); + Com folder; + check(service->GetFolder(Str(L"\\"), folder.out())); + Com existing; + HRESULT found = folder->GetTask(Str(L"KSwitcher"), existing.out()); + if (FAILED(found) && !missing(found)) + check(found); + if (mode != 2) + { + if (SUCCEEDED(found)) + check(existing->put_Enabled(VARIANT_FALSE)); + runEntry(mode == 1); + return; + } + auto sid = currentUserSid(); + Str user(sid.c_str()); + Com definition; + check(service->NewTask(0, definition.out())); + Com principal; + check(definition->get_Principal(principal.out())); + check(principal->put_UserId(user)); + check(principal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN)); + check(principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST)); + Com settings; + check(definition->get_Settings(settings.out())); + check(settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE)); + check(settings->put_StopIfGoingOnBatteries(VARIANT_FALSE)); + check(settings->put_ExecutionTimeLimit(Str(L"PT0S"))); + check(settings->put_MultipleInstances(TASK_INSTANCES_IGNORE_NEW)); + Com triggers; + check(definition->get_Triggers(triggers.out())); + Com trigger; + check(triggers->Create(TASK_TRIGGER_LOGON, trigger.out())); + Com logon; + check(trigger->QueryInterface(IID_ILogonTrigger, (void **)logon.out())); + check(logon->put_UserId(user)); + Com actions; + check(definition->get_Actions(actions.out())); + Com action; + check(actions->Create(TASK_ACTION_EXEC, action.out())); + Com exec; + check(action->QueryInterface(IID_IExecAction, (void **)exec.out())); + auto path = executable(); + check(exec->put_Path(Str(path.c_str()))); + check(exec->put_WorkingDirectory(Str(std::filesystem::path(path).parent_path().c_str()))); + VARIANT account; + VariantInit(&account); + account.vt = VT_BSTR; + account.bstrVal = user; + Com installed; + check(folder->RegisterTaskDefinition(Str(L"KSwitcher"), definition.p, TASK_CREATE_OR_UPDATE, account, + empty, TASK_LOGON_INTERACTIVE_TOKEN, empty, installed.out())); + runEntry(false); +} +void setAutostart(bool enabled, bool elevated) +{ + int mode = enabled ? (elevated ? 2 : 1) : 0; + if (IsUserAnAdmin()) + { + configureAutostart(mode); + return; + } + // Ordinary startup needs no UAC unless an elevated task must be changed. + if (mode != 2) + { + Com service; + HRESULT hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, + (void **)service.out()); + VARIANT empty; + VariantInit(&empty); + Com folder; + Com task; + if (SUCCEEDED(hr)) + hr = service->Connect(empty, empty, empty, empty); + if (SUCCEEDED(hr)) + hr = service->GetFolder(Str(L"\\"), folder.out()); + if (SUCCEEDED(hr)) + hr = folder->GetTask(Str(L"KSwitcher"), task.out()); + if (missing(hr)) + { + runEntry(mode == 1); + return; + } + check(hr); + } + auto file = executable(), + args = L"--configure-autostart=" + std::to_wstring(mode) + L" " + currentUserSid(); + SHELLEXECUTEINFOW request{sizeof(request)}; + request.fMask = SEE_MASK_NOCLOSEPROCESS; + request.lpVerb = L"runas"; + request.lpFile = file.c_str(); + request.lpParameters = args.c_str(); + request.nShow = SW_HIDE; + if (!ShellExecuteExW(&request) || !request.hProcess) + throw std::runtime_error("Administrator approval was cancelled or unavailable"); + // Continue servicing sent hook messages while UAC/helper runs, without + // dispatching another Settings Save or global command reentrantly. + DWORD wait{}; + while ((wait = MsgWaitForMultipleObjects(1, &request.hProcess, FALSE, INFINITE, QS_SENDMESSAGE)) == + WAIT_OBJECT_0 + 1) + { + MSG message; + PeekMessageW(&message, nullptr, WM_NULL, WM_NULL, PM_NOREMOVE); + } + DWORD code = 1; + GetExitCodeProcess(request.hProcess, &code); + CloseHandle(request.hProcess); + if (wait != WAIT_OBJECT_0 || code != 0) + throw std::runtime_error("Elevated autostart was not changed"); +} +} // namespace ks diff --git a/src/core.cpp b/src/core.cpp new file mode 100644 index 0000000..1378b44 --- /dev/null +++ b/src/core.cpp @@ -0,0 +1,467 @@ +#include "core.h" +#include +#include +#include +#include + +namespace ks +{ +int HotkeyHook::update(UINT key, bool down, UINT modifiers, + const std::vector> &bindings) +{ + if (key >= 256) + return 0; + if (pressed[key]) + { + if (!down) + pressed[key] = false; + return -1; + } + if (down) + for (const auto &[command, hotkey] : bindings) + if (hotkey.key == key && hotkey.modifiers == modifiers) + { + pressed[key] = true; + return command; + } + return 0; +} +bool letter(wchar_t c) +{ + WORD type{}; + GetStringTypeW(CT_CTYPE1, &c, 1, &type); + return (type & C1_ALPHA) != 0; +} +std::wstring lower(std::wstring s) +{ + if (!s.empty()) + CharLowerBuffW(s.data(), (DWORD)s.size()); + return s; +} +std::wstring upper(std::wstring s) +{ + if (!s.empty()) + CharUpperBuffW(s.data(), (DWORD)s.size()); + return s; +} +int Layout::code(wchar_t c) const +{ + auto p = normal.find(c); + if (p != normal.npos && c) + return (int)p; + p = shifted.find(c); + return p == shifted.npos || !c ? -1 : (int)(p + normal.size()); +} +wchar_t Layout::at(int c) const +{ + if (c < 0) + return 0; + return c < (int)normal.size() ? normal[c] + : c < (int)(normal.size() + shifted.size()) ? shifted[c - normal.size()] + : 0; +} +bool Layout::hasLetter(wchar_t c) const +{ + return letter(c) && code(c) >= 0; +} +std::wstring convert(const std::wstring &text, const Layout &from, const Layout &to, bool force) +{ + std::wstring result; + for (size_t i = 0; i < text.size();) + { + if (iswspace(text[i])) + { + result += text[i++]; + continue; + } + size_t end = i; + bool source = false, other = false; + while (end < text.size() && !iswspace(text[end])) + { + wchar_t c = text[end++]; + if (!letter(c)) + continue; + if (from.hasLetter(c) && !to.hasLetter(c)) + source = true; + else + other = true; + } + for (; i < end; ++i) + { + wchar_t mapped = (!force && !source && other) ? 0 : to.at(from.code(text[i])); + result += mapped ? mapped : text[i]; + } + } + return result; +} +int detect(const std::wstring &text, const std::vector &layouts, int preferred) +{ + int best = -1, score = 0; + auto check = [&](int i) + { + if (i < 0 || i >= (int)layouts.size() || !layouts[i].convertible) + return; + int n = + (int)std::count_if(text.begin(), text.end(), [&](wchar_t c) { return layouts[i].hasLetter(c); }); + if (n > score) + { + score = n; + best = i; + } + }; + check(preferred); + for (int i = 0; i < (int)layouts.size(); ++i) + check(i); + return best; +} +std::wstring convertTo(const std::wstring &text, const std::vector &layouts, int target, int hint) +{ + if (target < 0 || target >= (int)layouts.size() || !layouts[target].convertible) + return text; + const auto &to = layouts[target]; + if (hint >= 0 && hint < (int)layouts.size() && layouts[hint].convertible && + std::all_of( + text.begin(), text.end(), [&](wchar_t c) { return !letter(c) || layouts[hint].code(c) >= 0; })) + return hint == target ? text : convert(text, layouts[hint], to, true); + int best = -1, score = 0; + for (int i = 0; i < (int)layouts.size(); ++i) + if (i != target && layouts[i].convertible) + { + int n = (int)std::count_if(text.begin(), text.end(), [&](wchar_t c) + { return layouts[i].hasLetter(c) && !to.hasLetter(c); }); + if (n > score) + { + score = n; + best = i; + } + } + if (best < 0 && detect(text, layouts) < 0) + { + best = hint; + if (best < 0) + for (int i = 0; i < (int)layouts.size(); ++i) + if (i != target && layouts[i].convertible) + { + best = i; + break; + } + } + return best < 0 ? text : convert(text, layouts[best], to); +} +std::wstring cycleCase(const std::wstring &text) +{ + if (text == upper(text)) + return lower(text); + if (text != lower(text)) + return upper(text); + auto out = lower(text); + bool start = true; + for (auto &c : out) + { + if (letter(c)) + { + if (start) + c = upper(std::wstring(1, c))[0]; + start = false; + } + else + start = !iswdigit(c) && c != L'\'' && c != L'’'; + } + return out; +} +std::wstring transliterate(const std::wstring &text) +{ + static const std::wstring ru = L"абвгдеёжзийклмнопрстуфхцчшщъыьэюя"; + static const std::vector lat = {L"a", L"b", L"v", L"g", L"d", L"e", L"yo", L"zh", L"z", + L"i", L"y", L"k", L"l", L"m", L"n", L"o", L"p", L"r", + L"s", L"t", L"u", L"f", L"kh", L"ts", L"ch", L"sh", L"shch", + L"", L"y", L"", L"e", L"yu", L"ya"}; + bool cyr = std::any_of(text.begin(), text.end(), [](wchar_t c) { return c >= 0x400 && c <= 0x4ff; }); + std::wstring out, lo = lower(text); + if (cyr) + { + bool uk = lo.find_first_of(L"ґіїє") != lo.npos; + const std::map overrides = {{L'г', L"h"}, {L'ґ', L"g"}, {L'и', L"y"}, + {L'і', L"i"}, {L'ї', L"yi"}, {L'є', L"ye"}, + {L'\'', L""}, {L'’', L""}}; + for (size_t i = 0; i < text.size(); ++i) + { + std::wstring m; + auto p = ru.find(lo[i]); + auto u = overrides.find(lo[i]); + if (uk && u != overrides.end()) + { + m = u->second; + if (i && letter(text[i - 1])) + { + if (lo[i] == L'ї') + m = L"i"; + if (lo[i] == L'є') + m = L"ie"; + } + } + else if (p != ru.npos) + m = lat[p]; + else + { + out += text[i]; + continue; + } + if (text[i] != lo[i] && !m.empty()) + { + bool all = + (i + 1 < text.size() && text[i + 1] != lo[i + 1]) || (i > 0 && text[i - 1] != lo[i - 1]); + if (all) + m = upper(m); + else + m[0] = upper(m.substr(0, 1))[0]; + } + out += m; + } + } + else + { + static const std::vector> reverse = { + {L"shch", L"щ"}, {L"zh", L"ж"}, {L"kh", L"х"}, {L"ts", L"ц"}, {L"ch", L"ч"}, {L"sh", L"ш"}, + {L"yo", L"ё"}, {L"yu", L"ю"}, {L"ya", L"я"}, {L"ye", L"е"}, {L"a", L"а"}, {L"b", L"б"}, + {L"v", L"в"}, {L"w", L"в"}, {L"g", L"г"}, {L"d", L"д"}, {L"e", L"е"}, {L"z", L"з"}, + {L"i", L"и"}, {L"y", L"й"}, {L"k", L"к"}, {L"q", L"к"}, {L"l", L"л"}, {L"m", L"м"}, + {L"n", L"н"}, {L"o", L"о"}, {L"p", L"п"}, {L"r", L"р"}, {L"s", L"с"}, {L"t", L"т"}, + {L"u", L"у"}, {L"f", L"ф"}, {L"h", L"х"}, {L"c", L"к"}, {L"x", L"кс"}, {L"j", L"дж"}}; + for (size_t i = 0; i < text.size();) + { + bool found = false; + for (const auto &[a, b] : reverse) + if (lo.compare(i, a.size(), a) == 0) + { + auto m = b; + if (text[i] != lo[i]) + { + bool all = a.size() > 1 ? text[i + a.size() - 1] != lo[i + a.size() - 1] + : i + 1 < text.size() && text[i + 1] != lo[i + 1]; + if (all) + m = upper(m); + else + m[0] = upper(m.substr(0, 1))[0]; + } + out += m; + i += a.size(); + found = true; + break; + } + if (!found) + out += text[i++]; + } + } + return out; +} +Template parseTemplate(const std::wstring &text) +{ + Template t; + size_t cursor = std::wstring::npos; + for (size_t i = 0; i < text.size();) + { + if (text.compare(i, 10, L"{{cursor}}\0", 10) == 0) + { + t.text += L"{cursor}"; + i += 10; + } + else if (text.compare(i, 8, L"{cursor}") == 0) + { + cursor = t.text.size(); + ++t.cursors; + i += 8; + } + else + t.text += text[i++]; + } + if (cursor != text.npos) + t.suffix = t.text.substr(cursor); + return t; +} +namespace +{ +const std::vector> &namedKeys() +{ + static const std::vector> names = { + {VK_SPACE, L"Space"}, {VK_TAB, L"Tab"}, + {VK_RETURN, L"Enter"}, {VK_ESCAPE, L"Escape"}, + {VK_BACK, L"Backspace"}, {VK_DELETE, L"Delete"}, + {VK_INSERT, L"Insert"}, {VK_PAUSE, L"Pause"}, + {VK_SCROLL, L"ScrollLock"}, {VK_HOME, L"Home"}, + {VK_END, L"End"}, {VK_PRIOR, L"PageUp"}, + {VK_NEXT, L"PageDown"}, {VK_LEFT, L"Left"}, + {VK_RIGHT, L"Right"}, {VK_UP, L"Up"}, + {VK_DOWN, L"Down"}, {VK_SNAPSHOT, L"PrintScreen"}, + {VK_OEM_1, L"Semicolon"}, {VK_OEM_2, L"Slash"}, + {VK_OEM_3, L"Backtick"}, {VK_OEM_4, L"LeftBracket"}, + {VK_OEM_5, L"Backslash"}, {VK_OEM_6, L"RightBracket"}, + {VK_OEM_7, L"Quote"}, {VK_OEM_PLUS, L"Equal"}, + {VK_OEM_MINUS, L"Minus"}, {VK_OEM_COMMA, L"Comma"}, + {VK_OEM_PERIOD, L"Period"}, {VK_MULTIPLY, L"Multiply"}, + {VK_ADD, L"Add"}, {VK_SUBTRACT, L"Subtract"}, + {VK_DIVIDE, L"Divide"}, {VK_DECIMAL, L"Decimal"}}; + return names; +} +} // namespace +std::wstring Hotkey::format() const +{ + if (!key) + return {}; + std::wstring result; + if (modifiers & MOD_CONTROL) + result += L"Ctrl+"; + if (modifiers & MOD_ALT) + result += L"Alt+"; + if (modifiers & MOD_SHIFT) + result += L"Shift+"; + if (modifiers & MOD_WIN) + result += L"Win+"; + if ((key >= L'A' && key <= L'Z') || (key >= L'0' && key <= L'9')) + return result + wchar_t(key); + if (key >= VK_F1 && key <= VK_F24) + return result + L"F" + std::to_wstring(key - VK_F1 + 1); + if (key >= VK_NUMPAD0 && key <= VK_NUMPAD9) + return result + L"Num" + std::to_wstring(key - VK_NUMPAD0); + for (auto &[vk, name] : namedKeys()) + if (key == vk) + return result + name; + return {}; +} +std::optional Hotkey::parse(std::wstring s) +{ + Hotkey h; + if (s.empty()) + return h; + s = lower(s); + size_t pos = 0; + while (true) + { + size_t end = s.find(L'+', pos); + auto token = s.substr(pos, end == s.npos ? s.npos : end - pos); + if (token == L"ctrl") + h.modifiers |= MOD_CONTROL; + else if (token == L"alt") + h.modifiers |= MOD_ALT; + else if (token == L"shift") + h.modifiers |= MOD_SHIFT; + else if (token == L"win") + h.modifiers |= MOD_WIN; + else + { + if (h.key) + return {}; + if (token.size() == 1 && ((token[0] >= L'a' && token[0] <= L'z') || iswdigit(token[0]))) + h.key = upper(token)[0]; + else if (token.size() == 4 && token.substr(0, 3) == L"num" && token[3] >= L'0' && + token[3] <= L'9') + h.key = VK_NUMPAD0 + token[3] - L'0'; + else if (token.size() >= 2 && token[0] == L'f') + { + wchar_t *tail{}; + long n = wcstol(token.c_str() + 1, &tail, 10); + if (*tail || n < 1 || n > 24) + return {}; + h.key = VK_F1 + (UINT)n - 1; + } + else + { + for (auto &[key, name] : namedKeys()) + if (lower(name) == token) + { + h.key = key; + break; + } + if (!h.key) + return {}; + } + } + if (end == s.npos) + break; + pos = end + 1; + } + return h.key ? std::optional(h) : std::nullopt; +} +std::string utf8(const std::wstring &s) +{ + if (s.empty()) + return {}; + int n = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(), (int)s.size(), nullptr, 0, nullptr, + nullptr); + if (!n) + throw std::runtime_error("Invalid Unicode"); + std::string out(n, 0); + WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n, nullptr, nullptr); + return out; +} +std::wstring wide(const std::string &s) +{ + if (s.empty()) + return {}; + int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), (int)s.size(), nullptr, 0); + if (!n) + throw std::runtime_error("Invalid UTF-8"); + std::wstring out(n, 0); + MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n); + return out; +} +void Run::reset() +{ + keys.clear(); + window = nullptr; + layout = nullptr; + overflow = false; +} +void Run::record(wchar_t c, HWND w, HKL l, ULONGLONG now) +{ + if (!w || c < 32) + { + reset(); + return; + } + if (window != w || layout != l || now - at >= 2000 || + (!keys.empty() && iswspace(keys.back()) && !iswspace(c))) + reset(); + window = w; + layout = l; + at = now; + if (overflow) + { + if (iswspace(c)) + reset(); + return; + } + if (keys.empty() && iswspace(c)) + return; + if (keys.size() >= 512) + { + keys.clear(); + overflow = true; + return; + } + keys += c; +} +void Run::backspace(HWND w, ULONGLONG now) +{ + if (w != window || now - at >= 2000 || overflow) + reset(); + else if (!keys.empty()) + keys.pop_back(); + at = now; +} +int Run::match(const std::wstring &text, const std::vector &layouts, const Layout &physical) const +{ + if (text.size() != keys.size() || overflow) + return -1; + for (int i = 0; i < (int)layouts.size(); ++i) + if (layouts[i].convertible) + { + std::wstring expected; + for (wchar_t c : keys) + expected += c == L' ' ? L' ' : layouts[i].at(physical.code(c)); + if (lower(expected) == lower(text)) + return i; + } + return -1; +} +} // namespace ks diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..d74fe1b --- /dev/null +++ b/src/core.h @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace ks +{ +struct Layout +{ + HKL handle{}; + std::wstring id, name, normal, shifted; + bool convertible = true; + int code(wchar_t ch) const; + wchar_t at(int code) const; + bool hasLetter(wchar_t ch) const; +}; +bool letter(wchar_t ch); +std::wstring lower(std::wstring text); +std::wstring upper(std::wstring text); +std::wstring convert(const std::wstring &, const Layout &, const Layout &, bool force = false); +int detect(const std::wstring &, const std::vector &, int preferred = -1); +std::wstring convertTo(const std::wstring &, const std::vector &, int target, int hint); +std::wstring cycleCase(const std::wstring &); +std::wstring transliterate(const std::wstring &); +struct Snippet +{ + std::wstring trigger, text; +}; +struct Template +{ + std::wstring text, suffix; + int cursors = 0; +}; +Template parseTemplate(const std::wstring &); +struct Hotkey +{ + UINT modifiers{}, key{}; + bool operator==(const Hotkey &) const = default; + static std::optional parse(std::wstring); + std::wstring format() const; +}; +struct Settings +{ + bool recent = true, switchLayout = true, history = true, autostart = false, awake = false; + bool elevatedAutostart = false; + int maxItems = 500, maxMiB = 200, maxImageMiB = 20; + std::wstring toggle = L"Ctrl+Alt+Shift+Space", casing = L"Ctrl+Alt+Shift+C", + translit = L"Ctrl+Alt+Shift+T", undo = L"Ctrl+Alt+Shift+Z", clipboard = L"Ctrl+Shift+V"; + std::vector cycle, direct, directIds; + std::vector excluded{L"KeePass", L"KeePassXC", L"1Password", L"Bitwarden"}; + std::vector snippets; +}; +// Consume repeats and key-up for intercepted chords, even if settings change. +struct HotkeyHook +{ + bool pressed[256]{}; + // 0: pass through, -1: consume only, positive: dispatch that action once. + int update(UINT key, bool down, UINT modifiers, const std::vector> &bindings); +}; +std::string utf8(const std::wstring &); +std::wstring wide(const std::string &); +struct Run +{ + HWND window{}; + HKL layout{}; + ULONGLONG at{}; + std::wstring keys; + bool overflow{}; + void reset(); + void record(wchar_t, HWND, HKL, ULONGLONG); + void backspace(HWND, ULONGLONG); + int match(const std::wstring &, const std::vector &, const Layout &physical) const; +}; +} // namespace ks diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..bb97cd6 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,86 @@ +#include "app.h" +#include +#include +int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int) +{ + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + int argc{}; + auto argv = CommandLineToArgvW(GetCommandLineW(), &argc); + std::wstring arg = argc > 1 ? argv[1] : L""; + if (arg.starts_with(L"--configure-autostart=")) + { + int result = 1; + try + { + if (argc != 3 || ks::currentUserSid() != argv[2] || !IsUserAnAdmin()) + throw std::runtime_error("Administrator approval for the same Windows account is required"); + if (arg != L"--configure-autostart=0" && arg != L"--configure-autostart=1" && + arg != L"--configure-autostart=2") + throw std::runtime_error("Invalid autostart mode"); + ks::configureAutostart(arg.back() - L'0'); + result = 0; + } + catch (const std::exception &e) + { + MessageBoxW(nullptr, ks::wide(e.what()).c_str(), L"KSwitcher autostart", MB_OK | MB_ICONERROR); + } + LocalFree(argv); + CoUninitialize(); + return result; + } + HANDLE mutex = CreateMutexW(nullptr, TRUE, L"Local\\KSwitcher.Native.Singleton"); + bool existing = GetLastError() == ERROR_ALREADY_EXISTS; + int cmd = ks::ShowHistory; + if (arg == L"--convert") + cmd = ks::Toggle; + else if (arg == L"--case") + cmd = ks::Case; + else if (arg == L"--transliterate") + cmd = ks::Translit; + else if (arg == L"--settings") + cmd = 20; + else if (arg == L"--snippets") + cmd = 21; + else if (arg == L"--exit") + cmd = 99; + LocalFree(argv); + if (existing) + { + HWND window = FindWindowW(L"KSwitcher.Main", L"KSwitcher"); + if (window) + { + COPYDATASTRUCT data{1, sizeof(cmd), &cmd}; + DWORD_PTR result{}; + SendMessageTimeoutW(window, WM_COPYDATA, 0, (LPARAM)&data, SMTO_ABORTIFHUNG, 1000, &result); + } + CloseHandle(mutex); + CoUninitialize(); + return 0; + } + if (arg == L"--exit") + { + CloseHandle(mutex); + CoUninitialize(); + return 0; + } + int result = 0; + try + { + ks::App app(instance, arg == L"--smoke-test"); + if (!arg.empty() && arg != L"--smoke-test") + app.startCommand = cmd; + result = app.execute(); + } + catch (const std::exception &e) + { + MessageBoxW(nullptr, ks::wide(e.what()).c_str(), L"KSwitcher could not start", MB_OK | MB_ICONERROR); + result = 1; + } + if (mutex) + { + ReleaseMutex(mutex); + CloseHandle(mutex); + } + CoUninitialize(); + return result; +} diff --git a/src/ocr.cpp b/src/ocr.cpp new file mode 100644 index 0000000..009e78e --- /dev/null +++ b/src/ocr.cpp @@ -0,0 +1,274 @@ +#include "ocr.h" +#include +#include +#include +#include + +namespace ks +{ +namespace +{ +template struct Com +{ + T *p{}; + ~Com() + { + if (p) + p->Release(); + } + T *operator->() const + { + return p; + } + T **out() + { + return &p; + } + Com() = default; + Com(const Com &) = delete; + Com &operator=(const Com &) = delete; +}; +void check(HRESULT h) +{ + if (FAILED(h)) + { + char text[80]; + snprintf(text, sizeof(text), "Windows image/OCR service failed (0x%08lx)", (unsigned long)h); + throw std::runtime_error(text); + } +} +struct String +{ + HSTRING value{}; + explicit String(const wchar_t *s) + { + check(WindowsCreateString(s, (UINT32)wcslen(s), &value)); + } + ~String() + { + WindowsDeleteString(value); + } +}; +GUID guid(const wchar_t *text) +{ + GUID result{}; + check(IIDFromString(text, &result)); + return result; +} +template void factory(const wchar_t *name, const wchar_t *iid, Com &out) +{ + String s(name); + check(RoGetActivationFactory(s.value, guid(iid), (void **)out.out())); +} +} // namespace +// Minimal, strongly typed Windows Runtime ABI declarations. No CLR or C++/WinRT runtime is needed. +// Interface identities/method order follow Microsoft's Windows SDK metadata (Windows.Media.Ocr, +// Windows.Graphics.Imaging, Windows.Storage.Streams, Windows.Foundation). +struct Buffer : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Capacity(UINT32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE Length(UINT32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetLength(UINT32) = 0; +}; +struct BufferFactory : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Create(UINT32, Buffer **) = 0; +}; +struct BufferBytes : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE Data(BYTE **) = 0; +}; +struct BitmapStatics : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Copy(IInspectable *, IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE Convert(IInspectable *, INT32, IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE ConvertAlpha(IInspectable *, INT32, INT32, IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE FromBuffer(Buffer *, INT32, INT32, INT32, IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE FromBufferAlpha(Buffer *, INT32, INT32, INT32, INT32, + IInspectable **) = 0; +}; +struct OcrResult : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Lines(IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE Angle(IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE Text(HSTRING *) = 0; +}; +struct OcrOperation : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE PutCompleted(IUnknown *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetCompleted(IUnknown **) = 0; + virtual HRESULT STDMETHODCALLTYPE Results(OcrResult **) = 0; +}; +struct AsyncInfo : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Id(UINT32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE Status(INT32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE ErrorCode(HRESULT *) = 0; + virtual HRESULT STDMETHODCALLTYPE Cancel() = 0; + virtual HRESULT STDMETHODCALLTYPE Close() = 0; +}; +struct OcrEngine : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE Recognize(IInspectable *, OcrOperation **) = 0; + virtual HRESULT STDMETHODCALLTYPE Language(IInspectable **) = 0; +}; +struct OcrStatics : IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE MaxDimension(UINT32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE Languages(IInspectable **) = 0; + virtual HRESULT STDMETHODCALLTYPE Supported(IInspectable *, boolean *) = 0; + virtual HRESULT STDMETHODCALLTYPE FromLanguage(IInspectable *, OcrEngine **) = 0; + virtual HRESULT STDMETHODCALLTYPE FromProfile(OcrEngine **) = 0; +}; +Pixels decodeImage(const Clip &clip, UINT maxWidth, UINT maxHeight) +{ + Bytes encoded; + for (const auto &f : clip.formats) + if (f.name == L"PNG") + { + encoded = f.data; + break; + } + if (encoded.empty()) + for (const auto &f : clip.formats) + if ((f.id == CF_DIB || f.id == CF_DIBV5) && f.data.size() >= 40) + { + BITMAPINFOHEADER h; + memcpy(&h, f.data.data(), 40); + if (h.biSize < 40 || h.biSize > f.data.size()) + continue; + uint64_t colors = h.biClrUsed ? h.biClrUsed : (h.biBitCount <= 8 ? 1ull << h.biBitCount : 0); + uint64_t offset = sizeof(BITMAPFILEHEADER) + h.biSize + 4 * colors + + (h.biSize == 40 && h.biCompression == BI_BITFIELDS ? 12 : 0); + if (offset > sizeof(BITMAPFILEHEADER) + f.data.size()) + throw std::runtime_error("Invalid bitmap header"); + BITMAPFILEHEADER file{}; + file.bfType = 0x4d42; + file.bfSize = (DWORD)(sizeof(file) + f.data.size()); + file.bfOffBits = (DWORD)offset; + encoded.resize(sizeof(file)); + memcpy(encoded.data(), &file, sizeof(file)); + encoded.insert(encoded.end(), f.data.begin(), f.data.end()); + break; + } + if (encoded.empty()) + throw std::runtime_error("This clipboard item has no supported image"); + Com wic; + check(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, + (void **)wic.out())); + Com stream; + check(wic->CreateStream(stream.out())); + check(stream->InitializeFromMemory(encoded.data(), (DWORD)encoded.size())); + Com decoder; + check(wic->CreateDecoderFromStream(stream.p, nullptr, WICDecodeMetadataCacheOnLoad, decoder.out())); + Com frame; + check(decoder->GetFrame(0, frame.out())); + Pixels pixels; + check(frame->GetSize(&pixels.width, &pixels.height)); + if (!pixels.width || !pixels.height || uint64_t(pixels.width) * pixels.height > 32 * 1024 * 1024) + throw std::runtime_error("Image is too large to decode safely"); + Com converter; + Com scaler; + IWICBitmapSource *source = frame.p; + if (maxWidth && maxHeight && (pixels.width > maxWidth || pixels.height > maxHeight)) + { + const double ratio = std::min(double(maxWidth) / pixels.width, double(maxHeight) / pixels.height); + pixels.width = std::max(1u, UINT(pixels.width * ratio)); + pixels.height = std::max(1u, UINT(pixels.height * ratio)); + check(wic->CreateBitmapScaler(scaler.out())); + check(scaler->Initialize(frame.p, pixels.width, pixels.height, WICBitmapInterpolationModeFant)); + source = scaler.p; + } + check(wic->CreateFormatConverter(converter.out())); + check(converter->Initialize(source, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0, + WICBitmapPaletteTypeCustom)); + pixels.bgra.resize(size_t(pixels.width) * pixels.height * 4); + check(converter->CopyPixels(nullptr, pixels.width * 4, (UINT)pixels.bgra.size(), pixels.bgra.data())); + return pixels; +} +Format imageDib(const Pixels &p) +{ + BITMAPINFOHEADER h{}; + h.biSize = sizeof(h); + h.biWidth = (LONG)p.width; + h.biHeight = -(LONG)p.height; + h.biPlanes = 1; + h.biBitCount = 32; + Format format; + format.id = CF_DIB; + format.data.resize(sizeof(h)); + memcpy(format.data.data(), &h, sizeof(h)); + format.data.insert(format.data.end(), p.bgra.begin(), p.bgra.end()); + return format; +} +std::wstring recognizeText(const Clip &clip) +{ + check(RoInitialize(RO_INIT_MULTITHREADED)); + struct Apartment + { + ~Apartment() + { + RoUninitialize(); + } + } apartment; + auto pixels = decodeImage(clip); + Com ocr; + factory(L"Windows.Media.Ocr.OcrEngine", L"{5bffa85a-3384-3540-9940-699120d428a8}", ocr); + UINT32 max{}; + check(ocr->MaxDimension(&max)); + if (pixels.width > max || pixels.height > max) + throw std::runtime_error("Image exceeds Windows OCR's maximum dimensions"); + Com engine; + check(ocr->FromProfile(engine.out())); + if (!engine.p) + throw std::runtime_error("No Windows OCR language is installed for your profile"); + Com buffers; + factory(L"Windows.Storage.Streams.Buffer", L"{71af914d-c10f-484b-bc50-14bc623b3a27}", buffers); + Com buffer; + check(buffers->Create((UINT32)pixels.bgra.size(), buffer.out())); + check(buffer->SetLength((UINT32)pixels.bgra.size())); + Com bytes; + check(buffer->QueryInterface(guid(L"{905a0fef-bc53-11df-8c49-001e4fc686da}"), (void **)bytes.out())); + BYTE *memory{}; + check(bytes->Data(&memory)); + memcpy(memory, pixels.bgra.data(), pixels.bgra.size()); + Com bitmaps; + factory(L"Windows.Graphics.Imaging.SoftwareBitmap", L"{df0385db-672f-4a9d-806e-c2442f343e86}", bitmaps); + Com bitmap; + check(bitmaps->FromBufferAlpha(buffer.p, 87, (INT32)pixels.width, (INT32)pixels.height, 2, bitmap.out())); + Com operation; + check(engine->Recognize(bitmap.p, operation.out())); + Com info; + check(operation->QueryInterface(guid(L"{00000036-0000-0000-c000-000000000046}"), (void **)info.out())); + auto deadline = GetTickCount64() + 30000; + INT32 status{}; + do + { + check(info->Status(&status)); + if (status == 0) + { + if (GetTickCount64() > deadline) + { + info->Cancel(); + throw std::runtime_error("Windows OCR timed out"); + } + Sleep(20); + } + } while (status == 0); + if (status != 1) + { + HRESULT failure{}; + check(info->ErrorCode(&failure)); + check(FAILED(failure) ? failure : E_ABORT); + } + Com result; + check(operation->Results(result.out())); + HSTRING text{}; + check(result->Text(&text)); + UINT32 length{}; + const wchar_t *raw = WindowsGetStringRawBuffer(text, &length); + std::wstring out(raw, length); + WindowsDeleteString(text); + return out; +} +} // namespace ks diff --git a/src/ocr.h b/src/ocr.h new file mode 100644 index 0000000..00c4182 --- /dev/null +++ b/src/ocr.h @@ -0,0 +1,13 @@ +#pragma once +#include "platform.h" +namespace ks +{ +struct Pixels +{ + UINT width{}, height{}; + Bytes bgra; +}; +Pixels decodeImage(const Clip &, UINT maxWidth = 0, UINT maxHeight = 0); +Format imageDib(const Pixels &); +std::wstring recognizeText(const Clip &); +} // namespace ks diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000..a80a7bc --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,504 @@ +#include "platform.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ks +{ +namespace +{ +const UINT scans[] = {0x29, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 43}; +struct ClipboardLock +{ + ClipboardLock(HWND w) + { + for (int i = 0; i < 30; ++i) + { + if (OpenClipboard(w)) + return; + Sleep(10); + } + throw std::runtime_error("Clipboard is busy"); + } + ~ClipboardLock() + { + CloseClipboard(); + } +}; +bool globalFormat(UINT f) +{ + return f != CF_BITMAP && f != CF_PALETTE && f != CF_METAFILEPICT && f != CF_ENHMETAFILE && + !(f >= CF_PRIVATEFIRST && f <= CF_PRIVATELAST) && !(f >= CF_GDIOBJFIRST && f <= CF_GDIOBJLAST) && + f != CF_OWNERDISPLAY; +} +std::optional started(DWORD id) +{ + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, id); + if (!h) + return {}; + FILETIME c, e, k, u; + BOOL ok = GetProcessTimes(h, &c, &e, &k, &u); + CloseHandle(h); + return ok ? std::optional((ULONGLONG(c.dwHighDateTime) << 32) | c.dwLowDateTime) + : std::nullopt; +} +} // namespace +std::vector readLayouts() +{ + int n = GetKeyboardLayoutList(0, nullptr); + std::vector handles(n); + n = GetKeyboardLayoutList(n, handles.data()); + std::vector out; + for (int i = 0; i < n; ++i) + { + Layout l; + l.handle = handles[i]; + wchar_t id[32]; + swprintf_s(id, L"win:%08x", (unsigned)(uintptr_t)l.handle); + l.id = id; + wchar_t name[128]{}; + GetLocaleInfoW(MAKELCID(LOWORD((uintptr_t)l.handle), SORT_DEFAULT), LOCALE_SNATIVELANGNAME, name, + 128); + l.name = name; + if (l.name.empty()) + l.name = l.id; + l.name += L" (" + l.id.substr(4) + L")"; + l.convertible = + ImmGetIMEFileNameW(l.handle, nullptr, 0) == 0 && (((uintptr_t)l.handle >> 16) & 0xf000) != 0xe000; + for (bool shift : {false, true}) + for (UINT scan : scans) + { + BYTE state[256]{}; + if (shift) + state[VK_SHIFT] = 0x80; + wchar_t buf[8]{}; + UINT vk = MapVirtualKeyExW(scan, MAPVK_VSC_TO_VK_EX, l.handle); + int count = ToUnicodeEx(vk, scan, state, buf, 8, 4, l.handle); + wchar_t c = + count == 1 && buf[0] >= 32 && !(buf[0] >= 0xd800 && buf[0] <= 0xdfff) ? buf[0] : 0; + (shift ? l.shifted : l.normal) += c; + } + out.push_back(std::move(l)); + } + return out; +} +const Layout &physicalLayout() +{ + static const Layout l{nullptr, + L"physical", + L"Physical US", + L"`1234567890-=qwertyuiop[]asdfghjkl;'zxcvbnm,./\\", + L"~!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>?|", + true}; + return l; +} +wchar_t physicalKey(DWORD scan, bool shift) +{ + for (size_t i = 0; i < std::size(scans); ++i) + if (scans[i] == scan) + return (shift ? physicalLayout().shifted : physicalLayout().normal)[i]; + return 0; +} +HKL windowLayout(HWND w) +{ + return GetKeyboardLayout(GetWindowThreadProcessId(w, nullptr)); +} +bool activateWindow(HWND window) +{ + if (!IsWindow(window)) + return false; + if (SetForegroundWindow(window)) + return true; + DWORD foreground = GetWindowThreadProcessId(GetForegroundWindow(), nullptr); + DWORD current = GetCurrentThreadId(); + bool attached = foreground && foreground != current && AttachThreadInput(current, foreground, TRUE); + BringWindowToTop(window); + SetForegroundWindow(window); + if (attached) + AttachThreadInput(current, foreground, FALSE); + return GetForegroundWindow() == window; +} +bool webViewDescendant(DWORD owner, DWORD host, + const std::function>(DWORD)> &read) +{ + if (!owner || !host) + return false; + std::set seen; + while (owner != host) + { + if (!seen.insert(owner).second) + return false; + auto p = read(owner); + if (!p || lower(p->second) != L"msedgewebview2.exe" || !p->first) + return false; + owner = p->first; + } + return true; +} +bool ownsClipboard(HWND window) +{ + HWND owner = GetClipboardOwner(); + if (!owner || !window) + return false; + DWORD a{}, b{}; + GetWindowThreadProcessId(owner, &a); + GetWindowThreadProcessId(window, &b); + if (!a || !b) + return false; + if (a == b) + return true; + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) + return false; + std::map> entries; + PROCESSENTRY32W e{}; + e.dwSize = sizeof(e); + if (Process32FirstW(snapshot, &e)) + do + { + entries[e.th32ProcessID] = {e.th32ParentProcessID, e.szExeFile}; + } while (Process32NextW(snapshot, &e)); + CloseHandle(snapshot); + return webViewDescendant(a, b, + [&](DWORD id) -> std::optional> + { + auto it = entries.find(id); + if (it == entries.end()) + return {}; + auto child = started(id), parent = started(it->second.first); + if (!child || !parent || *parent > *child) + return {}; + return it->second; + }); +} +std::wstring processName(HWND window) +{ + DWORD pid{}; + GetWindowThreadProcessId(window, &pid); + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (!h) + return {}; + wchar_t path[32768]; + DWORD n = 32768; + BOOL ok = QueryFullProcessImageNameW(h, 0, path, &n); + CloseHandle(h); + return ok ? std::filesystem::path(std::wstring(path, n)).stem().wstring() : L""; +} +Clip readClipboard(HWND window, size_t limit) +{ + ClipboardLock lock(window); + Clip c; + size_t total = 0; + for (UINT f = EnumClipboardFormats(0); f; f = EnumClipboardFormats(f)) + { + if (!globalFormat(f)) + continue; + HANDLE handle = GetClipboardData(f); + if (!handle) + continue; + SIZE_T size = GlobalSize(handle); + if (!size) + continue; + if (size > limit - total) + throw std::runtime_error("Clipboard exceeds the configured size limit"); + const auto *p = static_cast(GlobalLock(handle)); + if (!p) + throw std::runtime_error("Cannot read clipboard format"); + Format format; + format.id = f; + format.data.assign(p, p + size); + GlobalUnlock(handle); + if (f >= 0xc000) + { + wchar_t name[256]{}; + GetClipboardFormatNameW(f, name, 256); + format.name = name; + } + total += size; + c.formats.push_back(std::move(format)); + } + return c; +} +DWORD writeClipboard(HWND window, const Clip &c) +{ + // Allocate all blocks before replacing any existing data. + std::vector> blocks; + try + { + for (const auto &f : c.formats) + { + if (f.data.empty()) + continue; + UINT id = f.name.empty() ? f.id : RegisterClipboardFormatW(f.name.c_str()); + if (!id) + throw std::runtime_error("Cannot register clipboard format"); + HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, f.data.size()); + if (!h) + throw std::bad_alloc(); + void *p = GlobalLock(h); + if (!p) + { + GlobalFree(h); + throw std::bad_alloc(); + } + memcpy(p, f.data.data(), f.data.size()); + GlobalUnlock(h); + blocks.emplace_back(id, h); + } + ClipboardLock lock(window); + if (!EmptyClipboard()) + throw std::runtime_error("Cannot replace clipboard"); + for (auto &[id, h] : blocks) + { + if (!SetClipboardData(id, h)) + throw std::runtime_error("Cannot write clipboard format"); + h = nullptr; + } + } + catch (...) + { + for (auto [id, h] : blocks) + if (h) + GlobalFree(h); + throw; + } + return GetClipboardSequenceNumber(); +} +Clip textClip(const std::wstring &text) +{ + Format f; + f.id = CF_UNICODETEXT; + f.data.resize((text.size() + 1) * sizeof(wchar_t)); + memcpy(f.data.data(), text.c_str(), f.data.size()); + return Clip{{std::move(f)}}; +} +std::wstring Clip::text() const +{ + for (const auto &f : formats) + if (f.id == CF_UNICODETEXT && f.data.size() >= 2) + { + std::wstring text(f.data.size() / 2, L'\0'); + memcpy(text.data(), f.data.data(), text.size() * 2); + auto pos = text.find(L'\0'); + if (pos != text.npos) + text.resize(pos); + return text; + } + return {}; +} +size_t Clip::size() const +{ + size_t n = 0; + for (const auto &f : formats) + n += f.data.size(); + return n; +} +bool chord(WORD key, UINT mods) +{ + std::vector inputs; + auto add = [&](WORD k, bool up) + { + INPUT i{}; + i.type = INPUT_KEYBOARD; + i.ki.wVk = k; + i.ki.dwFlags = up ? KEYEVENTF_KEYUP : 0; + if (k == VK_LEFT || k == VK_RIGHT || k == VK_INSERT) + i.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + inputs.push_back(i); + }; + for (auto [m, k] : {std::pair{MOD_CONTROL, VK_CONTROL}, + {MOD_ALT, VK_MENU}, + {MOD_SHIFT, VK_SHIFT}, + {MOD_WIN, VK_LWIN}}) + if (mods & m) + add(k, false); + add(key, false); + add(key, true); + for (auto [m, k] : {std::pair{MOD_WIN, VK_LWIN}, + {MOD_SHIFT, VK_SHIFT}, + {MOD_ALT, VK_MENU}, + {MOD_CONTROL, VK_CONTROL}}) + if (mods & m) + add(k, true); + return SendInput((UINT)inputs.size(), inputs.data(), sizeof(INPUT)) == inputs.size(); +} +bool needsSelectionDelay(HWND target) +{ + const auto name = lower(processName(target)); + return name == L"ms-teams" || name == L"msedgewebview2"; +} +bool selectPrevious(size_t count, const std::function &valid) +{ + if (count > 100000) + return false; + // Teams' WebView editor applies selection changes asynchronously. A burst of + // Shift+Left events can all use the same caret position and select only one + // character. Give each change time to settle before sending the next one. + const auto target = GetForegroundWindow(); + if (needsSelectionDelay(target)) + { + for (size_t i = 0; i < count; ++i) + { + if (!valid() || GetForegroundWindow() != target || !chord(VK_LEFT, MOD_SHIFT)) + return false; + Sleep(25); + } + return valid() && GetForegroundWindow() == target; + } + for (size_t offset = 0; offset < count; offset += 256) + { + if (!valid()) + return false; + std::vector inputs; + auto add = [&](WORD key, DWORD flags) + { + INPUT i{}; + i.type = INPUT_KEYBOARD; + i.ki.wVk = key; + i.ki.dwFlags = flags; + inputs.push_back(i); + }; + add(VK_SHIFT, 0); + for (size_t i = 0; i < std::min(256, count - offset); ++i) + { + add(VK_LEFT, KEYEVENTF_EXTENDEDKEY); + add(VK_LEFT, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP); + } + add(VK_SHIFT, KEYEVENTF_KEYUP); + if (SendInput((UINT)inputs.size(), inputs.data(), sizeof(INPUT)) != inputs.size()) + return false; + } + return valid(); +} +bool modifiersReleased() +{ + for (int k : {VK_CONTROL, VK_MENU, VK_SHIFT, VK_LWIN, VK_RWIN}) + if (GetAsyncKeyState(k) & 0x8000) + return false; + return true; +} +void releaseModifiers() +{ + std::vector inputs; + for (int k : {VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, VK_LSHIFT, VK_RSHIFT, VK_LWIN, VK_RWIN}) + if (GetAsyncKeyState(k) & 0x8000) + { + INPUT i{}; + i.type = INPUT_KEYBOARD; + i.ki.wVk = (WORD)k; + i.ki.dwFlags = KEYEVENTF_KEYUP; + inputs.push_back(i); + } + if (!inputs.empty()) + SendInput((UINT)inputs.size(), inputs.data(), sizeof(INPUT)); +} +std::wstring executable() +{ + wchar_t path[32768]; + DWORD n = GetModuleFileNameW(nullptr, path, 32768); + if (!n || n == 32768) + throw std::runtime_error("Cannot locate executable"); + return std::wstring(path, n); +} +std::filesystem::path dataDirectory() +{ + auto folder = std::filesystem::path(executable()).parent_path() / L"data"; + std::error_code ec; + std::filesystem::create_directories(folder, ec); + HANDLE probe = CreateFileW((folder / L".probe").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, nullptr); + if (probe != INVALID_HANDLE_VALUE) + { + CloseHandle(probe); + return folder; + } + PWSTR base{}; + if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &base))) + throw std::runtime_error("Cannot locate user data folder"); + folder = std::filesystem::path(base) / L"KSwitcher"; + CoTaskMemFree(base); + std::filesystem::create_directories(folder); + return folder; +} +void atomicWrite(const std::filesystem::path &path, const Bytes &bytes) +{ + auto tmp = path; + tmp += L".tmp"; + HANDLE h = + CreateFileW(tmp.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h == INVALID_HANDLE_VALUE) + throw std::runtime_error("Cannot open output file"); + DWORD written{}; + BOOL ok = WriteFile(h, bytes.data(), (DWORD)bytes.size(), &written, nullptr); + BOOL flushed = FlushFileBuffers(h); + CloseHandle(h); + if (!ok || written != bytes.size() || !flushed || + !MoveFileExW(tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) + { + DeleteFileW(tmp.c_str()); + throw std::runtime_error("Cannot save file atomically"); + } +} +Bytes readFile(const std::filesystem::path &path, size_t limit) +{ + auto n = std::filesystem::file_size(path); + if (n > limit) + throw std::runtime_error("File exceeds size limit"); + Bytes b((size_t)n); + std::ifstream f(path, std::ios::binary); + if (!f.read((char *)b.data(), (std::streamsize)b.size())) + throw std::runtime_error("Cannot read file"); + return b; +} +Bytes protect(const Bytes &bytes) +{ + DATA_BLOB in{(DWORD)bytes.size(), const_cast(bytes.data())}, out{}; + if (!CryptProtectData(&in, L"KSwitcher clipboard", nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, + &out)) + throw std::runtime_error("Cannot encrypt history"); + Bytes result(out.pbData, out.pbData + out.cbData); + LocalFree(out.pbData); + return result; +} +Bytes unprotect(const Bytes &bytes) +{ + DATA_BLOB in{(DWORD)bytes.size(), const_cast(bytes.data())}, out{}; + if (!CryptUnprotectData(&in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, &out)) + throw std::runtime_error("Cannot decrypt history for this Windows account"); + Bytes result(out.pbData, out.pbData + out.cbData); + LocalFree(out.pbData); + return result; +} +std::wstring lastError(const std::wstring &context) +{ + return context + L" (Windows error " + std::to_wstring(GetLastError()) + L")"; +} +void log(const std::wstring &message) +{ + static std::mutex gate; + std::lock_guard lock(gate); + try + { + auto path = dataDirectory() / L"kswitcher.log"; + std::error_code ec; + if (std::filesystem::file_size(path, ec) > 256 * 1024) + std::filesystem::remove(path, ec); + std::ofstream f(path, std::ios::app); + SYSTEMTIME t; + GetLocalTime(&t); + f << t.wYear << '-' << t.wMonth << '-' << t.wDay << ' ' << t.wHour << ':' << t.wMinute << ':' + << t.wSecond << ' ' << utf8(message) << '\n'; + } + catch (...) + { + } +} +} // namespace ks diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000..50a3b1c --- /dev/null +++ b/src/platform.h @@ -0,0 +1,51 @@ +#pragma once +#include "core.h" +#include +#include +#include +#include + +namespace ks +{ +using Bytes = std::vector; +struct Format +{ + UINT id{}; + std::wstring name; + Bytes data; +}; +struct Clip +{ + std::vector formats; + std::wstring text() const; + size_t size() const; +}; +std::vector readLayouts(); +const Layout &physicalLayout(); +wchar_t physicalKey(DWORD scan, bool shift); +HKL windowLayout(HWND); +bool activateWindow(HWND); +bool ownsClipboard(HWND); +bool webViewDescendant(DWORD owner, DWORD host, + const std::function>(DWORD)> &read); +std::wstring processName(HWND); +Clip readClipboard(HWND, size_t limit = 32 * 1024 * 1024); +DWORD writeClipboard(HWND, const Clip &); +Clip textClip(const std::wstring &); +bool chord(WORD key, UINT modifiers = 0); +bool needsSelectionDelay(HWND); +bool selectPrevious(size_t count, const std::function &valid); +bool modifiersReleased(); +void releaseModifiers(); +std::filesystem::path dataDirectory(); +void atomicWrite(const std::filesystem::path &, const Bytes &); +Bytes readFile(const std::filesystem::path &, size_t limit); +Bytes protect(const Bytes &); +Bytes unprotect(const Bytes &); +void setAutostart(bool, bool elevated = false); +std::wstring currentUserSid(); +void configureAutostart(int mode); +std::wstring executable(); +std::wstring lastError(const std::wstring &context); +void log(const std::wstring &message); +} // namespace ks diff --git a/src/richtext.cpp b/src/richtext.cpp new file mode 100644 index 0000000..75dc5e2 --- /dev/null +++ b/src/richtext.cpp @@ -0,0 +1,304 @@ +#include "richtext.h" +#include +#include +#include +#include + +namespace ks +{ +namespace +{ +std::string escape(const std::wstring &text) +{ + std::string out; + for (wchar_t c : text) + { + if (c == '\r') + continue; + if (c == '\n') + out += "\\line "; + else if (c == '\t') + out += "\\tab "; + else if (c == '\\' || c == '{' || c == '}') + { + out += '\\'; + out += char(c); + } + else if (c >= 32 && c < 127) + out += char(c); + else + out += "\\u" + std::to_string((int)(short)c) + "?"; + } + return out; +} +const std::string header = "{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Segoe UI;}}\\f0\\fs21\\uc1 "; +std::wstring entities(std::string_view input) +{ + std::wstring out; + for (size_t i = 0; i < input.size();) + { + if (input[i] != '&') + { + auto end = input.find('&', i); + if (end == input.npos) + end = input.size(); + out += wide(std::string(input.substr(i, end - i))); + i = end; + continue; + } + auto end = input.find(';', i + 1); + if (end != input.npos && end - i < 14) + { + auto name = std::string(input.substr(i + 1, end - i - 1)); + uint32_t c = 0; + if (name == "amp") + c = '&'; + else if (name == "lt") + c = '<'; + else if (name == "gt") + c = '>'; + else if (name == "quot") + c = '"'; + else if (name == "apos") + c = '\''; + else if (name == "nbsp") + c = 160; + else if (name.starts_with('#')) + try + { + size_t used{}; + bool hex = name.size() > 1 && (name[1] == 'x' || name[1] == 'X'); + auto digits = name.substr(hex ? 2 : 1); + auto n = std::stoul(digits, &used, hex ? 16 : 10); + if (used == digits.size() && n <= 0x10ffff && !(n >= 0xd800 && n <= 0xdfff)) + c = (uint32_t)n; + } + catch (...) + { + } + if (c) + { + if (c > 0xffff) + { + c -= 0x10000; + out += wchar_t(0xd800 + (c >> 10)); + out += wchar_t(0xdc00 + (c & 1023)); + } + else + out += wchar_t(c); + i = end + 1; + continue; + } + } + out += L'&'; + ++i; + } + return out; +} +std::string htmlRtf(std::string html) +{ + auto start = html.find(""); + auto end = html.find(""); + if (start != html.npos && end != html.npos && end >= start + 20) + html = html.substr(start + 20, end - start - 20); + else + { + auto tag = html.find('<'); + if (tag == html.npos) + return {}; + html.erase(0, tag); + } + std::string out = header; + std::vector stack; + std::string skip; + size_t chars = 0; + bool any = false; + for (size_t i = 0; i < html.size() && chars < 4096 && out.size() < 55000;) + { + if (html[i] != '<') + { + auto next = html.find('<', i); + if (next == html.npos) + next = html.size(); + if (skip.empty()) + { + auto text = entities(std::string_view(html).substr(i, next - i)); + text.resize(std::min(text.size(), 4096 - chars)); + chars += text.size(); + out += escape(text); + any |= !text.empty(); + } + i = next; + continue; + } + if (html.compare(i, 4, "", i + 4); + if (e == html.npos) + break; + i = e + 3; + continue; + } + size_t e = i + 1; + char quote = 0; + for (; e < html.size(); ++e) + { + char c = html[e]; + if (quote) + { + if (c == quote) + quote = 0; + } + else if (c == '\'' || c == '"') + quote = c; + else if (c == '>') + break; + } + if (e == html.size()) + break; + auto tag = html.substr(i + 1, e - i - 1); + i = e + 1; + std::transform(tag.begin(), tag.end(), tag.begin(), + [](unsigned char c) { return (char)std::tolower(c); }); + size_t p = 0; + while (p < tag.size() && isspace((unsigned char)tag[p])) + ++p; + bool close = p < tag.size() && tag[p] == '/'; + if (close) + ++p; + size_t begin = p; + while (p < tag.size() && (isalnum((unsigned char)tag[p]) || tag[p] == '-')) + ++p; + auto name = tag.substr(begin, p - begin); + if (!skip.empty()) + { + if (close && name == skip) + skip.clear(); + continue; + } + if (name == "script" || name == "style" || name == "head") + { + if (!close) + skip = name; + continue; + } + if (name.empty()) + continue; + bool block = name == "p" || name == "div" || name == "li" || name == "tr" || name == "blockquote"; + if (close) + { + auto it = std::find(stack.rbegin(), stack.rend(), name); + if (it != stack.rend()) + { + size_t n = std::distance(stack.rbegin(), it) + 1; + out.append(n, '}'); + stack.resize(stack.size() - n); + } + if (block && any) + out += "\\line "; + continue; + } + if (name == "br" || name == "hr") + { + out += "\\line "; + continue; + } + if (name == "img" || name == "meta" || name == "link" || name == "input" || tag.ends_with('/')) + continue; + if (stack.size() >= 128) + return {}; + if (block && any && !out.ends_with("\\line ")) + out += "\\line "; + out += '{'; + stack.push_back(name); + std::string compact = tag; + std::erase_if(compact, [](unsigned char c) { return isspace(c); }); + if (name == "b" || name == "strong" || compact.find("font-weight:bold") != compact.npos || + compact.find("font-weight:700") != compact.npos || + compact.find("font-weight:600") != compact.npos) + out += "\\b "; + if (name == "i" || name == "em" || compact.find("font-style:italic") != compact.npos) + out += "\\i "; + if (name == "u" || compact.find("text-decoration:underline") != compact.npos || + compact.find("text-decoration-line:underline") != compact.npos) + out += "\\ul "; + if (name == "s" || name == "strike" || name == "del" || compact.find("line-through") != compact.npos) + out += "\\strike "; + if (compact.find("font-weight:normal") != compact.npos || + compact.find("font-weight:400") != compact.npos) + out += "\\b0 "; + if (compact.find("font-style:normal") != compact.npos) + out += "\\i0 "; + if (name == "li") + out += "\\bullet\\tab "; + } + if (!any) + return {}; + out.append(stack.size(), '}'); + out += '}'; + return out; +} +struct Stream +{ + const std::string &text; + size_t at{}; +}; +DWORD CALLBACK readStream(DWORD_PTR cookie, LPBYTE buffer, LONG size, LONG *read) +{ + auto &s = *(Stream *)cookie; + *read = (LONG)std::min(size_t(size), s.text.size() - s.at); + memcpy(buffer, s.text.data() + s.at, *read); + s.at += *read; + return 0; +} +} // namespace +std::string richPreview(const Clip &clip) +{ + for (const auto &f : clip.formats) + if (f.name == L"Rich Text Format" && f.data.size() <= 65536) + { + std::string rtf(f.data.begin(), f.data.end()); + while (!rtf.empty() && rtf.back() == 0) + rtf.pop_back(); + if (rtf.starts_with("{\\rtf")) + return rtf; + } + for (const auto &f : clip.formats) + if (f.name == L"HTML Format" && f.data.size() <= 1024 * 1024) + { + try + { + auto rtf = htmlRtf(std::string(f.data.begin(), f.data.end())); + if (!rtf.empty()) + return rtf; + } + catch (...) + { + } + } + auto text = clip.text(); + if (text.size() > 4096) + text.resize(4096); + return header + escape(text) + '}'; +} +HWND createRichText(HWND parent, int id, bool visible) +{ + static HMODULE rich = LoadLibraryExW(L"Msftedit.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!rich) + return nullptr; + return CreateWindowExW(0, L"RICHEDIT50W", L"", + WS_CHILD | ES_MULTILINE | ES_READONLY | + (visible ? (WS_VISIBLE | WS_VSCROLL | ES_AUTOVSCROLL) : 0), + 0, 0, 1, 1, parent, (HMENU)(INT_PTR)id, GetModuleHandleW(nullptr), nullptr); +} +bool setRichText(HWND window, const std::string &text) +{ + if (!window) + return false; + Stream data{text}; + EDITSTREAM stream{(DWORD_PTR)&data, 0, readStream}; + SendMessageW(window, EM_STREAMIN, SF_RTF, (LPARAM)&stream); + SendMessageW(window, EM_EMPTYUNDOBUFFER, 0, 0); + return stream.dwError == 0; +} +} // namespace ks diff --git a/src/richtext.h b/src/richtext.h new file mode 100644 index 0000000..d6fbf72 --- /dev/null +++ b/src/richtext.h @@ -0,0 +1,8 @@ +#pragma once +#include "platform.h" +namespace ks +{ +std::string richPreview(const Clip &); +HWND createRichText(HWND parent, int id, bool visible); +bool setRichText(HWND, const std::string &); +} // namespace ks diff --git a/src/storage.cpp b/src/storage.cpp new file mode 100644 index 0000000..c85060c --- /dev/null +++ b/src/storage.cpp @@ -0,0 +1,427 @@ +#include "storage.h" +#include "json.hpp" +#include "ocr.h" +#include +#include +#include + +namespace ks +{ +using Json = nlohmann::json; +namespace +{ +Bytes jsonBytes(const Json &j) +{ + auto s = j.dump(2); + return Bytes(s.begin(), s.end()); +} +Json parse(const Bytes &bytes) +{ + return Json::parse(bytes.begin(), bytes.end()); +} +std::wstring str(const Json &j, const char *key, const std::wstring &fallback = L"") +{ + return j.contains(key) ? wide(j.at(key).get()) : fallback; +} +void put32(Bytes &b, uint32_t n) +{ + for (int i = 0; i < 4; ++i) + b.push_back((unsigned char)(n >> (8 * i))); +} +uint32_t get32(const Bytes &b, size_t &p) +{ + if (b.size() - p < 4) + throw std::runtime_error("Truncated clipboard record"); + uint32_t n = 0; + for (int i = 0; i < 4; ++i) + n |= uint32_t(b[p++]) << (8 * i); + return n; +} +bool validId(const std::wstring &id) +{ + return !id.empty() && id.size() < 80 && id.find_first_not_of(L"0123456789abcdef-") == id.npos; +} +} // namespace +void validateSettings(const Settings &s) +{ + std::vector used; + std::vector keys{s.toggle, s.casing, s.translit, s.undo, s.clipboard}; + keys.insert(keys.end(), s.direct.begin(), s.direct.end()); + for (auto &text : keys) + { + auto key = Hotkey::parse(text); + if (!key) + throw std::runtime_error("Invalid shortcut; use e.g. Ctrl+Alt+Shift+Space"); + if (!key->key) + continue; + if ((key->key == VK_DELETE && + (key->modifiers & (MOD_CONTROL | MOD_ALT)) == (MOD_CONTROL | MOD_ALT)) || + (key->key == 'L' && key->modifiers == MOD_WIN)) + throw std::runtime_error("Windows protects Ctrl+Alt+Delete and Win+L; choose another shortcut"); + if (std::find(used.begin(), used.end(), *key) != used.end()) + throw std::runtime_error("Keyboard shortcuts must be unique"); + used.push_back(*key); + } + if (s.maxItems < 10 || s.maxItems > 10000 || s.maxMiB < 1 || s.maxMiB > 2048 || s.maxImageMiB < 1 || + s.maxImageMiB > 64) + throw std::runtime_error("History limits: 10–10000 items, 1–2048 MiB total, 1–64 MiB per image"); + std::set seen; + for (const auto &snippet : s.snippets) + { + if (snippet.trigger.size() < 2 || snippet.trigger.size() > 64 || + snippet.trigger.find_first_of(L"\r\n\t ") != snippet.trigger.npos) + throw std::runtime_error("Snippet triggers must contain 2–64 characters without whitespace"); + if (!seen.insert(lower(snippet.trigger)).second) + throw std::runtime_error("Duplicate snippet trigger"); + auto t = parseTemplate(snippet.text); + if (t.text.empty() || t.cursors > 1) + throw std::runtime_error("Each snippet needs text and at most one {cursor} marker"); + } +} +Settings loadSettings(const std::filesystem::path &path, const std::vector &layouts) +{ + Settings s; + for (size_t i = 0; i < layouts.size(); ++i) + { + if (layouts[i].convertible) + s.cycle.push_back(layouts[i].id); + s.direct.push_back(i < 3 ? L"Ctrl+Alt+Shift+" + std::to_wstring(i + 1) : L""); + s.directIds.push_back(layouts[i].id); + } + if (!std::filesystem::exists(path)) + return s; + auto j = parse(readFile(path, 4 * 1024 * 1024)); + auto format = j.value("format", std::string()); + if (format != "kswitcher-settings" && format != "typlo-settings") + throw std::runtime_error("Not a KSwitcher or Typlo settings file"); + s.recent = j.value("convertLastWord", true); + s.switchLayout = j.value("switchSystemLayout", true); + s.history = j.value("historyEnabled", true); + s.autostart = j.value("startWithSystem", false); + s.elevatedAutostart = j.value("autostartAsAdministrator", false); + s.awake = j.value("preventScreenLock", false); + s.maxItems = std::clamp(j.value("maxItems", 500), 10, 10000); + s.maxMiB = std::clamp(j.value("maxTotalMegabytes", 200), 1, 2048); + s.maxImageMiB = std::clamp(j.value("maxImageMegabytes", 20), 1, 64); + s.toggle = str(j, "toggleHotkey", s.toggle); + s.casing = str(j, "caseHotkey", s.casing); + s.translit = str(j, "translitHotkey", s.translit); + s.undo = str(j, "undoHotkey", s.undo); + s.clipboard = str(j, "clipboardHotkey", s.clipboard); + if (j.contains("layouts")) + { + s.cycle.clear(); + for (auto &id : j.at("layouts")) + { + auto w = wide(id.get()); + if (std::find(s.cycle.begin(), s.cycle.end(), w) == s.cycle.end()) + s.cycle.push_back(w); + } + } + if (j.contains("layoutHotkeys")) + { + std::fill(s.direct.begin(), s.direct.end(), L""); + for (auto &key : j.at("layoutHotkeys")) + for (size_t i = 0; i < layouts.size(); ++i) + if (layouts[i].id == str(key, "layoutId")) + s.direct[i] = str(key, "hotkey"); + } + if (j.contains("excludedApps")) + { + s.excluded.clear(); + for (auto &name : j.at("excludedApps")) + s.excluded.push_back(wide(name.get())); + } + if (j.contains("snippets")) + for (auto &sn : j.at("snippets")) + s.snippets.push_back({str(sn, "trigger"), str(sn, "text")}); + validateSettings(s); + return s; +} +void saveSettings(const std::filesystem::path &path, const Settings &s) +{ + validateSettings(s); + Json j = {{"format", "kswitcher-settings"}, + {"schemaVersion", 1}, + {"convertLastWord", s.recent}, + {"switchSystemLayout", s.switchLayout}, + {"autostartAsAdministrator", s.elevatedAutostart}, + {"historyEnabled", s.history}, + {"startWithSystem", s.autostart}, + {"preventScreenLock", s.awake}, + {"maxItems", s.maxItems}, + {"maxTotalMegabytes", s.maxMiB}, + {"maxImageMegabytes", s.maxImageMiB}, + {"toggleHotkey", utf8(s.toggle)}, + {"caseHotkey", utf8(s.casing)}, + {"translitHotkey", utf8(s.translit)}, + {"undoHotkey", utf8(s.undo)}, + {"clipboardHotkey", utf8(s.clipboard)}}; + j["layouts"] = Json::array(); + for (auto &id : s.cycle) + j["layouts"].push_back(utf8(id)); + j["layoutHotkeys"] = Json::array(); + for (size_t i = 0; i < std::min(s.directIds.size(), s.direct.size()); ++i) + j["layoutHotkeys"].push_back({{"layoutId", utf8(s.directIds[i])}, {"hotkey", utf8(s.direct[i])}}); + j["snippets"] = Json::array(); + for (auto &sn : s.snippets) + j["snippets"].push_back({{"trigger", utf8(sn.trigger)}, {"text", utf8(sn.text)}}); + j["excludedApps"] = Json::array(); + for (auto &name : s.excluded) + j["excludedApps"].push_back(utf8(name)); + atomicWrite(path, jsonBytes(j)); +} +Bytes encodeClip(const Clip &clip) +{ + Bytes b; + put32(b, 0x3143534b); + put32(b, (uint32_t)clip.formats.size()); + for (const auto &f : clip.formats) + { + auto name = utf8(f.name); + put32(b, f.id); + put32(b, (uint32_t)name.size()); + put32(b, (uint32_t)f.data.size()); + b.insert(b.end(), name.begin(), name.end()); + b.insert(b.end(), f.data.begin(), f.data.end()); + } + return b; +} +Clip decodeClip(const Bytes &b) +{ + size_t p = 0; + if (get32(b, p) != 0x3143534b) + throw std::runtime_error("Unknown clipboard record"); + auto count = get32(b, p); + if (count > 1024) + throw std::runtime_error("Too many clipboard formats"); + Clip clip; + for (uint32_t i = 0; i < count; ++i) + { + Format f; + f.id = get32(b, p); + auto name = get32(b, p), size = get32(b, p); + if (name > 4096 || name > b.size() - p) + throw std::runtime_error("Invalid clipboard format name"); + f.name = wide(std::string((const char *)b.data() + p, name)); + p += name; + if (size > b.size() - p) + throw std::runtime_error("Truncated clipboard data"); + f.data.assign(b.begin() + p, b.begin() + p + size); + p += size; + clip.formats.push_back(std::move(f)); + } + if (p != b.size()) + throw std::runtime_error("Trailing clipboard data"); + return clip; +} +History::History(const std::filesystem::path &folder) : folder_(folder) +{ + std::filesystem::create_directories(folder_); + auto index = folder_ / L"index.dat"; + if (!std::filesystem::exists(index)) + return; + auto j = parse(unprotect(readFile(index, 16 * 1024 * 1024))); + if (!j.is_array() || j.size() > 10000) + throw std::runtime_error("Invalid history index"); + for (auto &e : j) + { + Entry item{str(e, "id"), + str(e, "preview"), + str(e, "app"), + e.value("bytes", uint64_t{}), + e.value("at", uint64_t{}), + e.value("pinned", false), + e.value("image", false), + e.value("files", false)}; + if (validId(item.id) && std::filesystem::exists(folder_ / (item.id + L".dat"))) + entries_.push_back(std::move(item)); + } +} +void History::saveIndex() +{ + Json j = Json::array(); + for (const auto &e : entries_) + j.push_back({{"id", utf8(e.id)}, + {"preview", utf8(e.preview)}, + {"app", utf8(e.app)}, + {"bytes", e.bytes}, + {"at", e.at}, + {"pinned", e.pinned}, + {"image", e.image}, + {"files", e.files}}); + atomicWrite(folder_ / L"index.dat", protect(jsonBytes(j))); +} +std::vector History::entries() const +{ + std::lock_guard lock(mutex_); + return entries_; +} +Clip History::load(const std::wstring &id) const +{ + if (!validId(id)) + throw std::runtime_error("Invalid history ID"); + return decodeClip(unprotect(readFile(folder_ / (id + L".dat"), 80 * 1024 * 1024))); +} +void History::prune(const Settings &s) +{ + uint64_t total = 0; + for (auto &e : entries_) + total += e.bytes; + for (size_t i = entries_.size(); i > 0;) + { + --i; + if (entries_.size() <= (size_t)s.maxItems && total <= uint64_t(s.maxMiB) * 1024 * 1024) + break; + if (entries_[i].pinned) + continue; + total -= entries_[i].bytes; + std::error_code ec; + std::filesystem::remove(folder_ / (entries_[i].id + L".dat"), ec); + entries_.erase(entries_.begin() + i); + } +} +bool sameClipboardImage(const Clip &a, const Clip &b) +{ + // OLE DataObject / Ole Private Data change when Snipping Tool republishes + // the same capture. They are transport metadata, not image identity. + auto image = [](const Clip &c) -> const Format * + { + if (!c.text().empty()) + return nullptr; + for (const auto &f : c.formats) + if (f.id == CF_HDROP) + return nullptr; + for (const auto &f : c.formats) + if (f.name == L"PNG" && !f.data.empty()) + return &f; + for (UINT id : {UINT(CF_DIBV5), UINT(CF_DIB)}) + for (const auto &f : c.formats) + if (f.id == id && !f.data.empty()) + return &f; + return nullptr; + }; + const auto *left = image(a), *right = image(b); + if (!left || !right) + return false; + if (left->id == right->id && left->name == right->name && left->data == right->data) + return true; + try + { + // Different encodings can still represent exactly the same pixels. + // These buffers are temporary; image payloads never stay in the index. + auto x = decodeImage(a), y = decodeImage(b); + return x.width == y.width && x.height == y.height && x.bgra == y.bgra; + } + catch (const std::exception &) + { + return false; // Undecodable data is not evidence of a duplicate. + } +} +void History::add(const Clip &clip, const std::wstring &app, const Settings &s) +{ + if (clip.formats.empty() || !clip.size()) + return; + auto data = encodeClip(clip); + if (data.size() > uint64_t(s.maxImageMiB) * 1024 * 1024) + return; + std::lock_guard lock(mutex_); + // Compare only the newest record; never keep image payloads in the resident index. + if (!entries_.empty()) + { + try + { + auto previous = load(entries_.front().id); + if (encodeClip(previous) == data || sameClipboardImage(previous, clip)) + return; + } + catch (...) + { + } + } + GUID guid{}; + if (FAILED(CoCreateGuid(&guid))) + throw std::runtime_error("Cannot generate history ID"); + wchar_t buf[40]; + StringFromGUID2(guid, buf, 40); + std::wstring id = lower(std::wstring(buf + 1, 36)); + auto encrypted = protect(data); + atomicWrite(folder_ / (id + L".dat"), encrypted); + auto text = clip.text(); + bool image = false, files = false; + for (auto &f : clip.formats) + { + image |= f.id == CF_DIB || f.id == CF_DIBV5 || f.name == L"PNG"; + files |= f.id == CF_HDROP; + } + if (text.empty()) + text = files ? L"[Files]" : image ? L"[Image]" : L"[Clipboard data]"; + for (auto &c : text) + if (c == L'\r' || c == L'\n' || c == L'\t') + c = L' '; + if (text.size() > 240) + text.resize(240); + FILETIME now; + GetSystemTimeAsFileTime(&now); + entries_.insert(entries_.begin(), + Entry{id, text, app, encrypted.size(), + (uint64_t(now.dwHighDateTime) << 32) | now.dwLowDateTime, false, image, files}); + prune(s); + saveIndex(); +} +void History::remove(const std::wstring &id) +{ + std::lock_guard lock(mutex_); + if (!validId(id)) + return; + auto it = std::find_if(entries_.begin(), entries_.end(), [&](auto &e) { return e.id == id; }); + if (it == entries_.end()) + return; + entries_.erase(it); + saveIndex(); + std::error_code ec; + std::filesystem::remove(folder_ / (id + L".dat"), ec); +} +void History::pin(const std::wstring &id) +{ + std::lock_guard lock(mutex_); + for (auto &e : entries_) + if (e.id == id) + e.pinned = !e.pinned; + saveIndex(); +} +void History::clear() +{ + std::lock_guard lock(mutex_); + auto old = entries_; + entries_.clear(); + saveIndex(); + for (auto &e : old) + { + std::error_code ec; + std::filesystem::remove(folder_ / (e.id + L".dat"), ec); + } +} +std::vector History::search(const std::wstring &query) const +{ + auto items = entries(); + if (query.empty()) + return items; + auto q = lower(query); + std::erase_if(items, + [&](auto &e) + { + if (lower(e.preview + L" " + e.app).find(q) != q.npos) + return false; + try + { + return lower(load(e.id).text()).find(q) == q.npos; + } + catch (...) + { + return true; + } + }); + return items; +} +} // namespace ks diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..d106e76 --- /dev/null +++ b/src/storage.h @@ -0,0 +1,36 @@ +#pragma once +#include "platform.h" +#include +namespace ks +{ +Settings loadSettings(const std::filesystem::path &, const std::vector &); +void saveSettings(const std::filesystem::path &, const Settings &); +void validateSettings(const Settings &); +struct Entry +{ + std::wstring id, preview, app; + uint64_t bytes{}, at{}; + bool pinned{}, image{}, files{}; +}; +class History +{ + std::filesystem::path folder_; + std::vector entries_; + mutable std::mutex mutex_; + void saveIndex(); + void prune(const Settings &); + + public: + explicit History(const std::filesystem::path &); + std::vector entries() const; + Clip load(const std::wstring &id) const; + void add(const Clip &, const std::wstring &app, const Settings &); + void remove(const std::wstring &); + void pin(const std::wstring &); + void clear(); + std::vector search(const std::wstring &) const; +}; +Bytes encodeClip(const Clip &); +Clip decodeClip(const Bytes &); +bool sameClipboardImage(const Clip &, const Clip &); +} // namespace ks diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..fb60940 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,1155 @@ +#include "../resources/resource.h" +#include "app.h" +#include "ocr.h" +#include "richtext.h" +#include +#include +#include +#include +#include +#include +#include + +namespace ks +{ +namespace +{ +constexpr UINT ResultsMessage = WM_APP + 40, PreviewMessage = WM_APP + 41; +int scale(HWND w, int n) +{ + return MulDiv(n, (int)GetDpiForWindow(w), 96); +} +HFONT font(HWND w) +{ + return CreateFontW(-scale(w, 14), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, + L"Segoe UI"); +} +HWND control(HWND parent, const wchar_t *cls, const wchar_t *title, DWORD style, int id, int x, int y, + int width, int height) +{ + HWND w = CreateWindowExW(wcscmp(cls, L"EDIT") == 0 ? WS_EX_CLIENTEDGE : 0, cls, title, + WS_CHILD | WS_VISIBLE | style, scale(parent, x), scale(parent, y), + scale(parent, width), scale(parent, height), parent, (HMENU)(INT_PTR)id, + GetModuleHandleW(nullptr), nullptr); + SendMessageW(w, WM_SETFONT, (WPARAM)GetPropW(parent, L"Font"), TRUE); + return w; +} +void label(HWND w, const wchar_t *text, int x, int y, int width = 200) +{ + control(w, L"STATIC", text, 0, 0, x, y, width, 22); +} +void button(HWND w, const wchar_t *text, int id, int x, int y, int width = 100) +{ + control(w, L"BUTTON", text, WS_TABSTOP | BS_PUSHBUTTON, id, x, y, width, 28); +} +HWND edit(HWND w, int id, int x, int y, int width, int height = 25, DWORD style = ES_AUTOHSCROLL) +{ + auto c = control(w, L"EDIT", L"", WS_TABSTOP | style, id, x, y, width, height); + SendMessageW(c, EM_SETLIMITTEXT, 1024 * 1024, 0); + return c; +} +LRESULT CALLBACK hotkeyProc(HWND w, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR, DWORD_PTR data) +{ + auto *app = (App *)data; + if (msg == WM_SETFOCUS) + { + app->hotkeyCapture = w; + SendMessageW(w, EM_SETSEL, 0, -1); + } + if (msg == WM_KILLFOCUS || msg == WM_NCDESTROY) + { + if (app->hotkeyCapture == w) + app->hotkeyCapture = nullptr; + } + if (msg == CaptureHotkeyMessage) + { + UINT mods = (UINT)lp; + if ((wp == VK_TAB && (mods == 0 || mods == MOD_SHIFT)) || (wp == VK_ESCAPE && mods == 0)) + { + SetFocus(GetNextDlgTabItem(GetParent(w), w, mods == MOD_SHIFT)); + return 0; + } + if ((wp == VK_BACK || wp == VK_DELETE) && mods == 0) + { + SetWindowTextW(w, L""); + return 0; + } + auto text = Hotkey{mods, (UINT)wp}.format(); + if (!text.empty()) + { + SetWindowTextW(w, text.c_str()); + SendMessageW(w, EM_SETSEL, 0, -1); + } + return 0; + } + if (msg == WM_CHAR || msg == WM_SYSCHAR || msg == WM_PASTE || msg == WM_CUT) + return 0; + return DefSubclassProc(w, msg, wp, lp); +} +void hotkeyEdit(App &app, HWND w, int id, int x, int y, int width) +{ + HWND field = edit(w, id, x, y, width, 25, ES_AUTOHSCROLL | ES_READONLY); + SendMessageW(field, EM_SETCUEBANNER, FALSE, (LPARAM)L"Click, then press a shortcut"); + SetWindowSubclass(field, hotkeyProc, 1, (DWORD_PTR)&app); +} +void checkbox(HWND w, const wchar_t *text, int id, int x, int y, int width = 300) +{ + control(w, L"BUTTON", text, WS_TABSTOP | BS_AUTOCHECKBOX, id, x, y, width, 24); +} +std::wstring value(HWND w, int id) +{ + HWND c = GetDlgItem(w, id); + int n = GetWindowTextLengthW(c); + std::wstring text(n + 1, L'\0'); + GetWindowTextW(c, text.data(), n + 1); + text.resize(n); + return text; +} +void value(HWND w, int id, const std::wstring &text) +{ + SetDlgItemTextW(w, id, text.c_str()); +} +bool checked(HWND w, int id) +{ + return IsDlgButtonChecked(w, id) == BST_CHECKED; +} +void check(HWND w, int id, bool yes) +{ + CheckDlgButton(w, id, yes ? BST_CHECKED : BST_UNCHECKED); +} +void error(HWND w, const std::exception &e) +{ + MessageBoxW(w, wide(e.what()).c_str(), L"KSwitcher", MB_OK | MB_ICONWARNING); +} +HWND makeWindow(App &app, const wchar_t *cls, const wchar_t *title, WNDPROC proc, int width, int height, + DWORD extra = 0) +{ + WNDCLASSW wc{}; + wc.hInstance = app.instance; + wc.lpfnWndProc = proc; + wc.lpszClassName = cls; + wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wc.hIcon = LoadIconW(app.instance, MAKEINTRESOURCEW(IDI_KSWITCHER)); + RegisterClassW(&wc); + UINT dpi = GetDpiForSystem(); + RECT rect{0, 0, MulDiv(width, dpi, 96), MulDiv(height, dpi, 96)}; + DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | extra; + AdjustWindowRectExForDpi(&rect, style, FALSE, WS_EX_APPWINDOW, dpi); + HWND w = + CreateWindowExW(WS_EX_APPWINDOW, cls, title, style, CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, app.instance, &app); + if (!w) + throw std::runtime_error(utf8(lastError(L"Cannot create native window"))); + SetWindowTextW(w, title); + auto smallIcon = (HICON)LoadImageW(app.instance, MAKEINTRESOURCEW(IDI_KSWITCHER), IMAGE_ICON, + GetSystemMetricsForDpi(SM_CXSMICON, dpi), + GetSystemMetricsForDpi(SM_CYSMICON, dpi), LR_SHARED); + SendMessageW(w, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon); + SetPropW(w, L"Font", font(w)); + SetPropW(w, L"Dpi", (HANDLE)(UINT_PTR)GetDpiForWindow(w)); + return w; +} +void resizeForDpi(HWND w, WPARAM wp, LPARAM lp) +{ + int previous = (int)(UINT_PTR)GetPropW(w, L"Dpi"); + int next = LOWORD(wp); + if (!previous) + previous = 96; + HFONT old = (HFONT)GetPropW(w, L"Font"); + HFONT replacement = font(w); + struct State + { + HWND parent; + int previous, next; + HFONT font; + } state{w, previous, next, replacement}; + EnumChildWindows( + w, + [](HWND child, LPARAM data) -> BOOL + { + auto &s = *(State *)data; + if (GetParent(child) != s.parent) + return TRUE; + RECT r; + GetWindowRect(child, &r); + MapWindowPoints(nullptr, s.parent, (POINT *)&r, 2); + SetWindowPos(child, nullptr, MulDiv(r.left, s.next, s.previous), + MulDiv(r.top, s.next, s.previous), MulDiv(r.right - r.left, s.next, s.previous), + MulDiv(r.bottom - r.top, s.next, s.previous), SWP_NOZORDER); + SendMessageW(child, WM_SETFONT, (WPARAM)s.font, TRUE); + return TRUE; + }, + (LPARAM)&state); + SetPropW(w, L"Font", replacement); + SetPropW(w, L"Dpi", (HANDLE)(UINT_PTR)next); + if (old) + DeleteObject(old); + auto *r = (RECT *)lp; + SetWindowPos(w, nullptr, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER); +} +std::filesystem::path pickFile(HWND owner, bool save) +{ + wchar_t path[32768]{}; + OPENFILENAMEW ofn{sizeof(ofn)}; + ofn.hwndOwner = owner; + ofn.lpstrFilter = L"Settings (*.json)\0*.json\0All files\0*.*\0"; + ofn.lpstrFile = path; + ofn.nMaxFile = 32768; + ofn.lpstrDefExt = L"json"; + ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | (save ? OFN_OVERWRITEPROMPT : OFN_FILEMUSTEXIST); + return (save ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) ? std::filesystem::path(path) + : std::filesystem::path(); +} +void disposeFont(HWND w) +{ + auto h = (HFONT)RemovePropW(w, L"Font"); + if (h) + DeleteObject(h); +} + +struct SearchResult +{ + uint64_t generation; + std::vector items; +}; +struct PreviewResult +{ + uint64_t generation; + std::wstring id; + Pixels pixels; + std::string rtf; +}; +struct HistoryView +{ + App *app{}; + std::vector items; + uint64_t generation{}; + std::map thumbnails; + std::map richTexts; + HWND rich{}; + std::set pending; +}; +std::wstring selected(HWND w, HistoryView *v) +{ + int i = (int)SendDlgItemMessageW(w, 102, LB_GETCURSEL, 0, 0); + return i >= 0 && i < (int)v->items.size() ? v->items[i].id : L""; +} +void search(HWND w, HistoryView *v) +{ + auto q = value(w, 101); + auto generation = ++v->generation; + v->pending.clear(); + auto *app = v->app; + app->enqueue( + [w, app, q, generation] + { + try + { + auto result = new SearchResult{generation, app->history->search(q)}; + if (!PostMessageW(w, ResultsMessage, 0, (LPARAM)result)) + delete result; + } + catch (const std::exception &e) + { + app->report(wide(e.what())); + } + }); +} +void requestPreview(HWND w, HistoryView *v, const Entry &entry) +{ + const auto id = entry.id; + const bool image = entry.image; + if (v->thumbnails.contains(id) || v->richTexts.contains(id) || !v->pending.insert(id).second) + return; + auto generation = v->generation; + auto *app = v->app; + app->enqueue( + [w, app, id, generation, image] + { + auto result = new PreviewResult{generation, id, {}, {}}; + try + { + auto clip = app->history->load(id); + if (image) + result->pixels = decodeImage(clip, 208, 128); + else + result->rtf = richPreview(clip); + } + catch (const std::exception &) + { + } + if (!PostMessageW(w, PreviewMessage, 0, (LPARAM)result)) + delete result; + }); +} +void drawHistoryRow(HWND w, HistoryView *v, const DRAWITEMSTRUCT &draw) +{ + if (draw.itemID >= v->items.size()) + return; + const auto &entry = v->items[draw.itemID]; + const bool selected = (draw.itemState & ODS_SELECTED) != 0; + HDC dc = draw.hDC; + int saved = SaveDC(dc); + auto box = draw.rcItem; + FillRect(dc, &box, GetSysColorBrush(selected ? COLOR_HIGHLIGHT : COLOR_WINDOW)); + SetBkMode(dc, TRANSPARENT); + SetTextColor(dc, GetSysColor(selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); + SelectObject(dc, GetPropW(w, L"Font")); + RECT icon{box.left + scale(w, 12), box.top + scale(w, 10), box.left + scale(w, 116), + box.bottom - scale(w, 10)}; + auto thumbnail = v->thumbnails.find(entry.id); + if (entry.image && thumbnail != v->thumbnails.end() && !thumbnail->second.bgra.empty()) + { + auto &p = thumbnail->second; + double ratio = + std::min(double(icon.right - icon.left) / p.width, double(icon.bottom - icon.top) / p.height); + int width = int(p.width * ratio), height = int(p.height * ratio); + BITMAPINFO info{}; + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = p.width; + info.bmiHeader.biHeight = -(LONG)p.height; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + SetStretchBltMode(dc, HALFTONE); + StretchDIBits(dc, icon.left + (icon.right - icon.left - width) / 2, + icon.top + (icon.bottom - icon.top - height) / 2, width, height, 0, 0, p.width, + p.height, p.bgra.data(), &info, DIB_RGB_COLORS, SRCCOPY); + } + else + { + FrameRect(dc, &icon, GetSysColorBrush(selected ? COLOR_HIGHLIGHTTEXT : COLOR_3DSHADOW)); + const wchar_t *glyph = entry.image ? L"▧" : entry.files ? L"≡" : L"Aa"; + DrawTextW(dc, glyph, -1, &icon, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + if (entry.image) + requestPreview(w, v, entry); + } + RECT title{icon.right + scale(w, 14), box.top + scale(w, 10), box.right - scale(w, 14), + box.bottom - scale(w, 30)}; + auto text = (entry.pinned ? L"★ " : L"") + (entry.image ? L"Image" + : entry.files ? L"Files" + : entry.preview); + bool formatted = false; + if (!entry.image && !entry.files) + { + auto rich = v->richTexts.find(entry.id); + if (rich != v->richTexts.end() && v->rich && !rich->second.empty()) + { + setRichText(v->rich, rich->second); + CHARFORMAT2W format{}; + format.cbSize = sizeof(format); + format.dwMask = CFM_COLOR; + format.crTextColor = GetSysColor(selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT); + SendMessageW(v->rich, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format); + SendMessageW(v->rich, EM_SETBKGNDCOLOR, 0, + GetSysColor(selected ? COLOR_HIGHLIGHT : COLOR_WINDOW)); + FORMATRANGE range{}; + range.hdc = range.hdcTarget = dc; + range.rc = {MulDiv(title.left, 1440, GetDeviceCaps(dc, LOGPIXELSX)), + MulDiv(title.top, 1440, GetDeviceCaps(dc, LOGPIXELSY)), + MulDiv(title.right, 1440, GetDeviceCaps(dc, LOGPIXELSX)), + MulDiv(title.bottom, 1440, GetDeviceCaps(dc, LOGPIXELSY))}; + range.rcPage = range.rc; + range.chrg = {0, -1}; + IntersectClipRect(dc, title.left, title.top, title.right, title.bottom); + SendMessageW(v->rich, EM_FORMATRANGE, TRUE, (LPARAM)&range); + SendMessageW(v->rich, EM_FORMATRANGE, FALSE, 0); + // Restore the row clipping before drawing its source label. + RestoreDC(dc, saved); + saved = SaveDC(dc); + SetBkMode(dc, TRANSPARENT); + SelectObject(dc, GetPropW(w, L"Font")); + formatted = true; + } + else + requestPreview(w, v, entry); + } + if (!formatted) + DrawTextW(dc, text.c_str(), -1, &title, DT_WORDBREAK | DT_END_ELLIPSIS | DT_NOPREFIX); + RECT detail{title.left, box.bottom - scale(w, 26), title.right, box.bottom - scale(w, 8)}; + SetTextColor(dc, GetSysColor(selected ? COLOR_HIGHLIGHTTEXT : COLOR_GRAYTEXT)); + auto subtitle = (entry.pinned ? L"★ " : L"") + std::to_wstring(draw.itemID + 1) + L" · " + entry.app; + DrawTextW(dc, subtitle.c_str(), -1, &detail, DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX); + if (draw.itemState & ODS_FOCUS) + { + InflateRect(&box, -2, -2); + DrawFocusRect(dc, &box); + } + RestoreDC(dc, saved); +} +LRESULT CALLBACK historyKey(HWND w, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR, DWORD_PTR data) +{ + HWND parent = (HWND)data; + if (msg == WM_KEYDOWN) + { + if (wp == VK_ESCAPE) + { + SendMessageW(parent, WM_CLOSE, 0, 0); + return 0; + } + if (wp == VK_RETURN) + { + SendMessageW(parent, WM_COMMAND, (GetKeyState(VK_SHIFT) & 0x8000) ? 111 : 110, 0); + return 0; + } + if (GetKeyState(VK_CONTROL) & 0x8000) + { + if (wp >= L'1' && wp <= L'9') + { + SendDlgItemMessageW(parent, 102, LB_SETCURSEL, wp - L'1', 0); + SendMessageW(parent, WM_COMMAND, 110, 0); + return 0; + } + if (wp == L'P') + { + SendMessageW(parent, WM_COMMAND, 112, 0); + return 0; + } + if (wp == L'O') + { + SendMessageW(parent, WM_COMMAND, 114, 0); + return 0; + } + } + if (GetDlgCtrlID(w) == 102 && wp == VK_DELETE) + { + SendMessageW(parent, WM_COMMAND, 113, 0); + return 0; + } + if (GetDlgCtrlID(w) == 101 && wp == VK_DOWN) + { + SetFocus(GetDlgItem(parent, 102)); + return 0; + } + } + return DefSubclassProc(w, msg, wp, lp); +} +LRESULT CALLBACK historyProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) +{ + auto *v = (HistoryView *)GetWindowLongPtrW(w, GWLP_USERDATA); + if (msg == WM_NCCREATE) + { + auto *app = (App *)((CREATESTRUCTW *)lp)->lpCreateParams; + v = new HistoryView; + v->app = app; + SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v); + return TRUE; + } + if (!v) + return DefWindowProcW(w, msg, wp, lp); + try + { + switch (msg) + { + case WM_COMMAND: + { + int id = LOWORD(wp); + if (id == 101 && HIWORD(wp) == EN_CHANGE) + { + SetTimer(w, 1, 180, nullptr); + return 0; + } + if (id == 102 && HIWORD(wp) == LBN_SELCHANGE) + { + return 0; + } + if (id == 102 && HIWORD(wp) == LBN_DBLCLK) + id = 110; + auto entry = selected(w, v); + if (id == 110 || id == 111) + { + if (!entry.empty()) + v->app->pasteEntry(entry, id == 111); + return 0; + } + if (id == 112 || id == 113) + { + if (!entry.empty()) + { + if (id == 112) + v->app->history->pin(entry); + else + v->app->history->remove(entry); + search(w, v); + } + return 0; + } + if (id == 115 && !entry.empty()) + { + v->app->recognizeEntry(entry); + return 0; + } + if (id == 114 && !entry.empty()) + { + showEntry(*v->app, entry); + return 0; + } + return 0; + } + case WM_TIMER: + KillTimer(w, 1); + search(w, v); + return 0; + case ResultsMessage: + { + std::unique_ptr result((SearchResult *)lp); + if (result->generation != v->generation) + return 0; + auto old = selected(w, v); + v->items = std::move(result->items); + SendDlgItemMessageW(w, 102, WM_SETREDRAW, FALSE, 0); + SendDlgItemMessageW(w, 102, LB_RESETCONTENT, 0, 0); + int select = 0; + for (size_t i = 0; i < v->items.size(); ++i) + { + auto &e = v->items[i]; + auto text = (e.pinned ? L"★ " : L"") + e.preview + L" — " + e.app; + SendDlgItemMessageW(w, 102, LB_ADDSTRING, 0, (LPARAM)text.c_str()); + if (e.id == old) + select = (int)i; + } + SendDlgItemMessageW(w, 102, LB_SETCURSEL, select, 0); + SendDlgItemMessageW(w, 102, WM_SETREDRAW, TRUE, 0); + InvalidateRect(GetDlgItem(w, 102), nullptr, TRUE); + return 0; + } + case PreviewMessage: + { + std::unique_ptr result((PreviewResult *)lp); + if (result->generation != v->generation || !IsWindowVisible(w)) + return 0; + v->pending.erase(result->id); + // At most ~3.3 MiB, irrespective of history length or source image size. + if (v->thumbnails.size() >= 32) + v->thumbnails.erase(v->thumbnails.begin()); + if (v->richTexts.size() >= 32) + v->richTexts.erase(v->richTexts.begin()); + if (!result->rtf.empty()) + v->richTexts[result->id] = std::move(result->rtf); + else + v->thumbnails[result->id] = std::move(result->pixels); + InvalidateRect(GetDlgItem(w, 102), nullptr, FALSE); + return 0; + } + case WM_MEASUREITEM: + if (((MEASUREITEMSTRUCT *)lp)->CtlID == 102) + { + ((MEASUREITEMSTRUCT *)lp)->itemHeight = scale(w, 88); + return TRUE; + } + break; + case WM_DRAWITEM: + if (((DRAWITEMSTRUCT *)lp)->CtlID == 102) + { + drawHistoryRow(w, v, *(DRAWITEMSTRUCT *)lp); + return TRUE; + } + break; + case WM_CONTEXTMENU: + { + HMENU menu = CreatePopupMenu(); + AppendMenuW(menu, MF_STRING, 110, L"Paste"); + AppendMenuW(menu, MF_STRING, 111, L"Paste plain text"); + AppendMenuW(menu, MF_STRING, 112, L"Pin / unpin"); + AppendMenuW(menu, MF_STRING, 113, L"Delete"); + AppendMenuW(menu, MF_STRING, 114, L"Open full item"); + AppendMenuW(menu, MF_STRING, 115, L"Copy text from image (Windows OCR)"); + POINT p; + GetCursorPos(&p); + int cmd = TrackPopupMenu(menu, TPM_RETURNCMD, p.x, p.y, 0, w, nullptr); + DestroyMenu(menu); + if (cmd) + SendMessageW(w, WM_COMMAND, cmd, 0); + return 0; + } + case WM_CLOSE: + ShowWindow(w, SW_HIDE); + v->thumbnails.clear(); + v->richTexts.clear(); + if (v->rich) + SetWindowTextW(v->rich, L""); + v->pending.clear(); + ++v->generation; + return 0; + case WM_DPICHANGED: + { + resizeForDpi(w, wp, lp); + SendDlgItemMessageW(w, 102, LB_SETITEMHEIGHT, 0, scale(w, 88)); + return 0; + } + case WM_NCDESTROY: + disposeFont(w); + v->app->historyWindow = nullptr; + delete v; + return DefWindowProcW(w, msg, wp, lp); + } + } + catch (const std::exception &e) + { + error(w, e); + } + return DefWindowProcW(w, msg, wp, lp); +} + +struct SettingsView +{ + App *app{}; + Settings draft; + std::vector order; + int selection = -1; + bool filling{}; +}; +void collectLayout(HWND w, SettingsView *v) +{ + if (v->selection < 0 || v->selection >= (int)v->order.size()) + return; + int index = v->order[v->selection]; + auto id = v->app->layouts[index].id; + auto &cycle = v->draft.cycle; + std::erase(cycle, id); + if (checked(w, 230)) + cycle.push_back(id); + if (v->draft.direct.size() < v->app->layouts.size()) + v->draft.direct.resize(v->app->layouts.size()); + v->draft.direct[index] = value(w, 231); +} +void layoutSelection(HWND w, SettingsView *v) +{ + v->selection = (int)SendDlgItemMessageW(w, 220, LB_GETCURSEL, 0, 0); + if (v->selection < 0) + return; + int i = v->order[v->selection]; + auto &l = v->app->layouts[i]; + check(w, 230, std::find(v->draft.cycle.begin(), v->draft.cycle.end(), l.id) != v->draft.cycle.end()); + EnableWindow(GetDlgItem(w, 230), l.convertible); + value(w, 231, i < (int)v->draft.direct.size() ? v->draft.direct[i] : L""); +} +void fillLayouts(HWND w, SettingsView *v) +{ + SendDlgItemMessageW(w, 220, LB_RESETCONTENT, 0, 0); + for (int i : v->order) + { + auto &l = v->app->layouts[i]; + bool on = std::find(v->draft.cycle.begin(), v->draft.cycle.end(), l.id) != v->draft.cycle.end(); + auto title = (on ? L"[✓] " : L"[ ] ") + l.name + (l.convertible ? L"" : L" — switch only"); + SendDlgItemMessageW(w, 220, LB_ADDSTRING, 0, (LPARAM)title.c_str()); + } + SendDlgItemMessageW(w, 220, LB_SETCURSEL, std::max(0, v->selection), 0); + layoutSelection(w, v); +} +void fillSettings(HWND w, SettingsView *v) +{ + v->filling = true; + auto &s = v->draft; + check(w, 201, s.recent); + check(w, 202, s.switchLayout); + check(w, 203, s.history); + check(w, 204, s.autostart); + check(w, 206, s.elevatedAutostart); + check(w, 205, s.awake); + value(w, 210, s.toggle); + value(w, 211, s.casing); + value(w, 212, s.translit); + value(w, 213, s.undo); + value(w, 214, s.clipboard); + value(w, 240, std::to_wstring(s.maxItems)); + value(w, 241, std::to_wstring(s.maxMiB)); + value(w, 242, std::to_wstring(s.maxImageMiB)); + std::wstring excluded; + for (auto &name : s.excluded) + { + if (!excluded.empty()) + excluded += L", "; + excluded += name; + } + value(w, 243, excluded); + v->order.clear(); + for (auto &id : s.cycle) + for (int i = 0; i < (int)v->app->layouts.size(); ++i) + if (v->app->layouts[i].id == id) + v->order.push_back(i); + for (int i = 0; i < (int)v->app->layouts.size(); ++i) + if (std::find(v->order.begin(), v->order.end(), i) == v->order.end()) + v->order.push_back(i); + v->selection = 0; + fillLayouts(w, v); + v->filling = false; +} +int integer(HWND w, int id) +{ + auto s = value(w, id); + size_t n{}; + int v = std::stoi(s, &n); + if (n != s.size()) + throw std::runtime_error("Enter a whole number for history limits"); + return v; +} +Settings collectSettings(HWND w, SettingsView *v) +{ + collectLayout(w, v); + auto s = v->draft; + s.recent = checked(w, 201); + s.switchLayout = checked(w, 202); + s.history = checked(w, 203); + s.autostart = checked(w, 204); + s.elevatedAutostart = checked(w, 206); + s.awake = checked(w, 205); + s.toggle = value(w, 210); + s.casing = value(w, 211); + s.translit = value(w, 212); + s.undo = value(w, 213); + s.clipboard = value(w, 214); + s.maxItems = integer(w, 240); + s.maxMiB = integer(w, 241); + s.maxImageMiB = integer(w, 242); + s.excluded.clear(); + std::wistringstream input(value(w, 243)); + std::wstring item; + while (std::getline(input, item, L',')) + { + auto first = item.find_first_not_of(L" \t"), last = item.find_last_not_of(L" \t"); + if (first != item.npos) + s.excluded.push_back(item.substr(first, last - first + 1)); + } + std::vector cycle; + for (int i : v->order) + { + auto id = v->app->layouts[i].id; + if (std::find(s.cycle.begin(), s.cycle.end(), id) != s.cycle.end()) + cycle.push_back(id); + } + s.cycle = cycle; + validateSettings(s); + return s; +} +LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_DPICHANGED) + { + resizeForDpi(w, wp, lp); + return 0; + } + auto *v = (SettingsView *)GetWindowLongPtrW(w, GWLP_USERDATA); + if (msg == WM_NCCREATE) + { + auto *app = (App *)((CREATESTRUCTW *)lp)->lpCreateParams; + v = new SettingsView{app, app->settings}; + SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v); + return TRUE; + } + if (!v) + return DefWindowProcW(w, msg, wp, lp); + try + { + switch (msg) + { + case WM_COMMAND: + { + int id = LOWORD(wp); + if (id == 220 && HIWORD(wp) == LBN_SELCHANGE && !v->filling) + { + collectLayout(w, v); + layoutSelection(w, v); + return 0; + } + if (id == 230) + { + collectLayout(w, v); + fillLayouts(w, v); + return 0; + } + if (id == 232 || id == 233) + { + collectLayout(w, v); + int next = v->selection + (id == 232 ? -1 : 1); + if (next >= 0 && next < (int)v->order.size()) + { + std::swap(v->order[next], v->order[v->selection]); + v->selection = next; + fillLayouts(w, v); + } + return 0; + } + if (id == IDOK) + { + if (v->app->busy) + throw std::runtime_error("Wait for the current text operation to finish"); + v->app->applySettings(collectSettings(w, v)); + DestroyWindow(w); + return 0; + } + if (id == IDCANCEL) + { + DestroyWindow(w); + return 0; + } + if (id == 250) + { + auto path = pickFile(w, false); + if (!path.empty()) + { + v->draft = loadSettings(path, v->app->layouts); + fillSettings(w, v); + } + return 0; + } + if (id == 251) + { + auto s = collectSettings(w, v); + auto path = pickFile(w, true); + if (!path.empty()) + saveSettings(path, s); + return 0; + } + if (id == 252) + { + showSnippets(*v->app); + return 0; + } + if (id == 253) + { + if (v->app->busy) + throw std::runtime_error("Wait for the current operation"); + v->draft = collectSettings(w, v); + v->app->reloadLayouts(); + v->draft.direct = v->app->settings.direct; + fillSettings(w, v); + return 0; + } + if (id == 254 && v->app->history) + { + if (MessageBoxW(w, L"Delete all clipboard history, including pinned items?", L"Clear history", + MB_YESNO | MB_ICONWARNING) == IDYES) + v->app->history->clear(); + return 0; + } + break; + } + case WM_CLOSE: + DestroyWindow(w); + return 0; + case WM_NCDESTROY: + disposeFont(w); + v->app->settingsWindow = nullptr; + delete v; + return DefWindowProcW(w, msg, wp, lp); + } + } + catch (const std::exception &e) + { + error(w, e); + } + return DefWindowProcW(w, msg, wp, lp); +} +struct SnippetView +{ + App *app; + std::vector snippets; + int selection = -1; +}; +void fillSnippets(HWND w, SnippetView *v) +{ + SendDlgItemMessageW(w, 301, LB_RESETCONTENT, 0, 0); + for (auto &sn : v->snippets) + SendDlgItemMessageW(w, 301, LB_ADDSTRING, 0, (LPARAM)sn.trigger.c_str()); + if (v->selection >= 0) + SendDlgItemMessageW(w, 301, LB_SETCURSEL, v->selection, 0); +} +void collectSnippet(HWND w, SnippetView *v) +{ + if (v->selection >= 0 && v->selection < (int)v->snippets.size()) + { + v->snippets[v->selection] = {value(w, 302), value(w, 303)}; + } +} +LRESULT CALLBACK snippetProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_DPICHANGED) + { + resizeForDpi(w, wp, lp); + return 0; + } + auto *v = (SnippetView *)GetWindowLongPtrW(w, GWLP_USERDATA); + if (msg == WM_NCCREATE) + { + auto *app = (App *)((CREATESTRUCTW *)lp)->lpCreateParams; + v = new SnippetView{app, app->settings.snippets}; + SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v); + return TRUE; + } + if (!v) + return DefWindowProcW(w, msg, wp, lp); + try + { + switch (msg) + { + case WM_COMMAND: + { + int id = LOWORD(wp); + if (id == 301 && HIWORD(wp) == LBN_SELCHANGE) + { + collectSnippet(w, v); + v->selection = (int)SendDlgItemMessageW(w, 301, LB_GETCURSEL, 0, 0); + if (v->selection >= 0) + { + value(w, 302, v->snippets[v->selection].trigger); + value(w, 303, v->snippets[v->selection].text); + } + return 0; + } + if (id == 310) + { + collectSnippet(w, v); + v->snippets.push_back({L";new", L""}); + v->selection = (int)v->snippets.size() - 1; + fillSnippets(w, v); + value(w, 302, L";new"); + value(w, 303, L""); + SetFocus(GetDlgItem(w, 302)); + return 0; + } + if (id == 311) + { + if (v->selection >= 0) + { + v->snippets.erase(v->snippets.begin() + v->selection); + v->selection = -1; + fillSnippets(w, v); + value(w, 302, L""); + value(w, 303, L""); + } + return 0; + } + if (id == 312) + { + SendDlgItemMessageW(w, 303, EM_REPLACESEL, TRUE, (LPARAM)L"{cursor}"); + return 0; + } + if (id == IDOK) + { + collectSnippet(w, v); + auto s = v->app->settings; + s.snippets = v->snippets; + if (v->app->busy) + throw std::runtime_error("Wait for the current operation"); + v->app->applySettings(s); + if (v->app->settingsWindow) + { + auto *parent = (SettingsView *)GetWindowLongPtrW(v->app->settingsWindow, GWLP_USERDATA); + parent->draft.snippets = s.snippets; + } + DestroyWindow(w); + return 0; + } + if (id == IDCANCEL) + { + DestroyWindow(w); + return 0; + } + break; + } + case WM_CLOSE: + DestroyWindow(w); + return 0; + case WM_NCDESTROY: + disposeFont(w); + v->app->snippetsWindow = nullptr; + delete v; + return DefWindowProcW(w, msg, wp, lp); + } + } + catch (const std::exception &e) + { + error(w, e); + } + return DefWindowProcW(w, msg, wp, lp); +} +LRESULT CALLBACK entryProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_SIZE) + { + MoveWindow(GetDlgItem(w, 1), 10, 10, LOWORD(lp) - 20, HIWORD(lp) - 20, TRUE); + return 0; + } + if (msg == WM_NCDESTROY) + disposeFont(w); + return DefWindowProcW(w, msg, wp, lp); +} +} // namespace +void refreshHistory(App &app) +{ + if (app.historyWindow) + { + auto *v = (HistoryView *)GetWindowLongPtrW(app.historyWindow, GWLP_USERDATA); + search(app.historyWindow, v); + } +} +void showHistory(App &app) +{ + if (!app.history) + { + app.report(L"Clipboard history is unavailable. See the log in the data folder."); + return; + } + HWND foreground = GetForegroundWindow(); + if (app.historyWindow && IsWindowVisible(app.historyWindow) && foreground == app.historyWindow) + { + auto *v = (HistoryView *)GetWindowLongPtrW(app.historyWindow, GWLP_USERDATA); + if (v->items.size() > 1) + app.pasteEntry(v->items[1].id, false); + return; + } + if (foreground != app.window && foreground != app.historyWindow) + app.pasteTarget = foreground; + if (!app.historyWindow) + { + HWND w = makeWindow(app, L"KSwitcher.History", L"Clipboard history", historyProc, 760, 500); + app.historyWindow = w; + ((HistoryView *)GetWindowLongPtrW(w, GWLP_USERDATA))->rich = createRichText(w, 105, false); + label(w, L"Search clipboard history", 16, 12, 500); + edit(w, 101, 16, 38, 728); + auto list = control(w, L"LISTBOX", L"", + WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER | + LBS_OWNERDRAWFIXED | LBS_HASSTRINGS, + 102, 16, 76, 728, 360); + SendMessageW(list, LB_SETITEMHEIGHT, 0, scale(w, 88)); + button(w, L"Paste", 110, 16, 452, 90); + button(w, L"Plain text", 111, 114, 452, 100); + button(w, L"Pin / unpin", 112, 222, 452, 110); + button(w, L"Delete", 113, 340, 452, 90); + button(w, L"Open item", 114, 438, 452, 100); + SetWindowSubclass(list, historyKey, 1, (DWORD_PTR)w); + SetWindowSubclass(GetDlgItem(w, 101), historyKey, 1, (DWORD_PTR)w); + } + POINT cursor; + GetCursorPos(&cursor); + MONITORINFO monitor{sizeof(monitor)}; + GetMonitorInfoW(MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST), &monitor); + RECT r; + GetWindowRect(app.historyWindow, &r); + int x = std::max(monitor.rcWork.left, std::min(cursor.x, monitor.rcWork.right - (r.right - r.left))); + int y = std::max(monitor.rcWork.top, std::min(cursor.y, monitor.rcWork.bottom - (r.bottom - r.top))); + SetWindowPos(app.historyWindow, HWND_TOP, x, y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); + SetForegroundWindow(app.historyWindow); + SetFocus(GetDlgItem(app.historyWindow, 101)); + refreshHistory(app); +} +void showSettings(App &app) +{ + if (app.settingsWindow) + { + ShowWindow(app.settingsWindow, SW_RESTORE); + SetForegroundWindow(app.settingsWindow); + return; + } + HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 780, 660); + app.settingsWindow = w; + label(w, L"Text conversion", 18, 16); + checkbox(w, L"Convert recently typed text when nothing is selected", 201, 18, 43, 440); + checkbox(w, L"Switch Windows input layout after conversion", 202, 18, 70, 440); + checkbox(w, L"Record clipboard history", 203, 18, 106); + checkbox(w, L"Start with Windows", 204, 18, 133); + checkbox(w, L"Start with Windows as administrator", 206, 18, 160, 395); + checkbox(w, L"Keep screen awake", 205, 18, 184); + label(w, L"Click a shortcut and press the desired keys", 420, 16, 350); + const wchar_t *names[] = {L"Next layout", L"Change case", L"Transliterate", L"Undo", + L"Clipboard history"}; + for (int i = 0; i < 5; ++i) + { + label(w, names[i], 420, 48 + i * 30, 115); + hotkeyEdit(app, w, 210 + i, 540, 44 + i * 30, 220); + } + label(w, L"Layout cycle — top to bottom", 18, 210, 400); + control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 220, + 18, 232, 470, 190); + button(w, L"Move up", 232, 504, 232, 120); + button(w, L"Move down", 233, 632, 232, 128); + checkbox(w, L"Include in cycle", 230, 504, 275, 240); + label(w, L"Direct layout shortcut", 504, 313, 240); + hotkeyEdit(app, w, 231, 504, 340, 256); + label(w, L"Backspace / Delete clears a shortcut", 504, 365, 256); + button(w, L"Refresh layouts", 253, 504, 384, 150); + label(w, L"History limits", 18, 444); + label(w, L"Items", 18, 478, 50); + edit(w, 240, 70, 474, 90); + label(w, L"Total MiB", 180, 478, 75); + edit(w, 241, 260, 474, 90); + label(w, L"Per item MiB", 370, 478, 100); + edit(w, 242, 475, 474, 80); + button(w, L"Clear history…", 254, 580, 474, 180); + label(w, L"Exclude applications (process names, comma-separated)", 18, 516, 700); + edit(w, 243, 18, 544, 742); + button(w, L"Import settings…", 250, 18, 606, 145); + button(w, L"Export settings…", 251, 173, 606, 145); + button(w, L"Snippets…", 252, 328, 606, 110); + button(w, L"Save", IDOK, 548, 606, 100); + button(w, L"Cancel", IDCANCEL, 660, 606, 100); + fillSettings(w, (SettingsView *)GetWindowLongPtrW(w, GWLP_USERDATA)); + ShowWindow(w, SW_SHOW); + SetForegroundWindow(w); +} +void showSnippets(App &app) +{ + if (app.snippetsWindow) + { + SetForegroundWindow(app.snippetsWindow); + return; + } + HWND w = makeWindow(app, L"KSwitcher.Snippets", L"Text snippets", snippetProc, 740, 520); + app.snippetsWindow = w; + label(w, L"Triggers", 16, 16); + control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 301, + 16, 44, 200, 380); + button(w, L"Add", 310, 16, 438, 90); + button(w, L"Delete", 311, 116, 438, 100); + label(w, L"Trigger (for example ;addr)", 236, 16, 470); + edit(w, 302, 236, 44, 488); + label(w, L"Replacement text", 236, 82, 480); + edit(w, 303, 236, 110, 488, 270, ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | WS_VSCROLL); + label(w, L"Use {cursor} once; {{cursor}} inserts the literal marker.", 236, 392, 488); + button(w, L"Insert cursor marker", 312, 236, 430, 190); + button(w, L"Save", IDOK, 510, 478, 100); + button(w, L"Cancel", IDCANCEL, 624, 478, 100); + fillSnippets(w, (SnippetView *)GetWindowLongPtrW(w, GWLP_USERDATA)); + ShowWindow(w, SW_SHOW); + SetForegroundWindow(w); +} +void showShortcuts(App &app) +{ + auto &s = app.settings; + std::wstring text = L"Next layout: " + s.toggle + L"\nChange case: " + s.casing + L"\nTransliterate: " + + s.translit + L"\nUndo: " + s.undo + L"\nClipboard history: " + s.clipboard + L"\n"; + for (size_t i = 0; i < std::min(app.layouts.size(), s.direct.size()); ++i) + if (!s.direct[i].empty()) + text += L"\n" + app.layouts[i].name + L": " + s.direct[i]; + text += L"\n\nHistory: Enter to paste, Shift+Enter for plain text,\nCtrl+1…9 to paste, Ctrl+P to pin, " + L"Delete to remove,\nCtrl+O to open the full item, Escape to close."; + MessageBoxW(app.window, text.c_str(), L"Keyboard shortcuts", MB_OK); +} +void showEntry(App &app, const std::wstring &id) +{ + try + { + auto clip = app.history->load(id); + auto text = clip.text(); + if (text.empty()) + { + MessageBoxW(app.historyWindow, + L"This item contains an image or files. Use the preview or paste it into the " + L"destination application.", + L"Clipboard item", MB_OK); + return; + } + HWND w = makeWindow(app, L"KSwitcher.Entry", L"Clipboard item", entryProc, 720, 500, + WS_THICKFRAME | WS_MAXIMIZEBOX); + auto field = edit(w, 1, 10, 10, 700, 480, ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | WS_VSCROLL); + SetWindowTextW(field, text.c_str()); + ShowWindow(w, SW_SHOW); + } + catch (const std::exception &e) + { + error(app.historyWindow, e); + } +} +} // namespace ks diff --git a/tests/integration.cpp b/tests/integration.cpp new file mode 100644 index 0000000..89a7ee2 --- /dev/null +++ b/tests/integration.cpp @@ -0,0 +1,192 @@ +#include "app.h" +#include + +namespace +{ +ks::App *app{}; +HWND host{}, field{}; +ks::Clip saved; +bool captured = false, failed = false; +int stage = 0, ru = -1, en = -1; +ULONGLONG deadline{}; +std::wstring sentinel = L"KSwitcher integration clipboard sentinel"; +void require(bool ok, const char *name) +{ + if (!ok) + throw std::runtime_error(name); + std::cout << "PASS " << name << '\n'; +} +std::wstring text() +{ + wchar_t buffer[2048]{}; + GetWindowTextW(field, buffer, 2048); + return buffer; +} +void prepare(const wchar_t *value, bool select) +{ + app->resetInput(); + SetWindowTextW(field, value); + ks::activateWindow(host); + SetFocus(field); + int end = GetWindowTextLengthW(field); + SendMessageW(field, EM_SETSEL, select ? 0 : end, end); + if (GetForegroundWindow() != host) + throw std::runtime_error("Test editor could not acquire foreground; no input was sent"); +} +void record(const std::wstring &keys) +{ + std::lock_guard lock(app->stateMutex); + for (wchar_t c : keys) + app->run.record(c, host, app->layouts[en].handle, GetTickCount64()); +} +void finish() +{ + KillTimer(host, 1); + if (captured) + { + try + { + app->ownSequence = ks::writeClipboard(app->window, saved); + } + catch (...) + { + failed = true; + } + } + DestroyWindow(host); + PostMessageW(app->window, WM_CLOSE, 0, 0); +} +LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_TIMER) + { + try + { + if (GetTickCount64() > deadline) + throw std::runtime_error("Native integration timed out"); + if (app->busy) + return 0; + switch (stage++) + { + case 0: + saved = ks::readClipboard(app->window, 64 * 1024 * 1024); + captured = true; + app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel)); + prepare(L"ghbdtn", true); + app->transform(ks::Direct + ru); + break; + case 1: + std::cout << "Observed editor: " << ks::utf8(text()) << "; busy=" << app->busy + << "; foreground=" << (GetForegroundWindow() == host) << "\n"; + require(text() == L"привет", "selection conversion through actual Ctrl+C / Ctrl+V"); + require(ks::readClipboard(app->window).text() == sentinel, "clipboard restored"); + app->transform(ks::Undo); + break; + case 2: + require(text() == L"ghbdtn", "verified undo"); + prepare(L"hello world", true); + app->transform(ks::Case); + break; + case 3: + require(text() == L"Hello World", "case conversion"); + app->transform(ks::Case); + break; + case 4: + require(text() == L"HELLO WORLD", "consecutive command reselects verified previous result"); + prepare(L"OLDghbdtn", false); + record(L"ghbdtn"); + app->transform(ks::Direct + ru); + break; + case 5: + std::cout << "Recent observed: " << ks::utf8(text()) << "\n"; + require(text() == L"OLDпривет", "recent-input conversion preserves adjacent older text"); + prepare(L"OLDabcdef", false); + record(L"ghbdtn"); + app->transform(ks::Direct + ru); + break; + case 6: + require(text() == L"OLDabcdef", "recent-input mismatch refuses replacement"); + app->settings.snippets = {{L";addr", L"LINE1\r\n{cursor}LINE2"}}; + prepare(L"prefix ;addr", false); + record(L";addr"); + app->transform(0, 0); + break; + case 7: + { + require(text() == L"prefix LINE1\r\nLINE2", "multiline snippet expansion"); + DWORD start{}, end{}; + SendMessageW(field, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); + require(start == end && start == 14, "verified snippet cursor placement"); + require(ks::readClipboard(app->window).text() == sentinel, + "clipboard restored after repeated copy transactions"); + finish(); + break; + } + } + } + catch (const std::exception &e) + { + failed = true; + std::cerr << "FAIL " << e.what() << '\n'; + finish(); + } + return 0; + } + if (msg == WM_CLOSE) + { + failed = true; + finish(); + return 0; + } + return DefWindowProcW(w, msg, wp, lp); +} +} // namespace +int main() +{ + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + auto root = std::filesystem::temp_directory_path() / + (L"KSwitcher-integration-" + std::to_wstring(GetCurrentProcessId())); + try + { + { + ks::App instance(GetModuleHandleW(nullptr), false, root); + app = &instance; + app->settings.history = false; + app->settings.switchLayout = false; + for (int i = 0; i < (int)app->layouts.size(); ++i) + { + auto language = LOWORD((uintptr_t)app->layouts[i].handle); + if (language == 0x409) + en = i; + if (language == 0x419) + ru = i; + } + if (en < 0 || ru < 0) + throw std::runtime_error( + "Integration test requires installed English US and Russian layouts"); + WNDCLASSW cls{}; + cls.hInstance = GetModuleHandleW(nullptr); + cls.lpfnWndProc = procedure; + cls.lpszClassName = L"KSwitcher.TestEditor"; + cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + RegisterClassW(&cls); + host = CreateWindowW(cls.lpszClassName, L"KSwitcher — automated test editor", + WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 650, 320, nullptr, nullptr, + cls.hInstance, nullptr); + field = + CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_WANTRETURN, + 20, 20, 590, 230, host, nullptr, cls.hInstance, nullptr); + deadline = GetTickCount64() + 30000; + SetTimer(host, 1, 100, nullptr); + app->execute(); + } + std::filesystem::remove_all(root); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + failed = true; + } + CoUninitialize(); + return failed ? 1 : 0; +} diff --git a/tests/ocr.cpp b/tests/ocr.cpp new file mode 100644 index 0000000..18b8b0b --- /dev/null +++ b/tests/ocr.cpp @@ -0,0 +1,53 @@ +#include "ocr.h" +#include +int main() +{ + CoInitializeEx(nullptr, COINIT_MULTITHREADED); + try + { + BITMAPINFO info{}; + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = 800; + info.bmiHeader.biHeight = -150; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + void *data{}; + HBITMAP bitmap = CreateDIBSection(nullptr, &info, DIB_RGB_COLORS, &data, nullptr, 0); + if (!bitmap) + throw std::runtime_error("Cannot create OCR fixture"); + HDC dc = CreateCompatibleDC(nullptr); + auto old = SelectObject(dc, bitmap); + RECT rect{0, 0, 800, 150}; + FillRect(dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); + HFONT font = CreateFontW(-54, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, 0, 0, + ANTIALIASED_QUALITY, 0, L"Arial"); + auto oldFont = SelectObject(dc, font); + SetTextColor(dc, RGB(0, 0, 0)); + SetBkColor(dc, RGB(255, 255, 255)); + TextOutW(dc, 20, 40, L"HELLO KSWITCHER", 15); + GdiFlush(); + ks::Pixels pixels{800, 150, ks::Bytes((BYTE *)data, (BYTE *)data + 800 * 150 * 4)}; + SelectObject(dc, oldFont); + SelectObject(dc, old); + DeleteObject(font); + DeleteObject(bitmap); + DeleteDC(dc); + ks::Clip clip{{ks::imageDib(pixels)}}; + auto decoded = ks::decodeImage(clip); + if (decoded.width != 800 || decoded.height != 150) + throw std::runtime_error("WIC decode size mismatch"); + auto text = ks::recognizeText(clip); + std::cout << "OCR: " << ks::utf8(text) << '\n'; + if (ks::upper(text).find(L"HELLO") == std::wstring::npos) + throw std::runtime_error("OCR fixture was not recognized"); + std::cout << "PASS native Windows OCR, DIB decoding and async result retrieval\n"; + CoUninitialize(); + return 0; + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + CoUninitialize(); + return 1; + } +} diff --git a/tests/tests.cpp b/tests/tests.cpp new file mode 100644 index 0000000..16ac5b6 --- /dev/null +++ b/tests/tests.cpp @@ -0,0 +1,275 @@ +#include "ocr.h" +#include "richtext.h" +#include "storage.h" +#include +#include +#include +#include + +namespace +{ +int passed = 0; +void require(bool ok, const char *name) +{ + if (!ok) + throw std::runtime_error(name); + ++passed; +} +void rejects(const std::function &f, const char *name) +{ + bool thrown = false; + try + { + f(); + } + catch (...) + { + thrown = true; + } + require(thrown, name); +} +} // namespace +int main() +{ + try + { + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + HWND richParent = CreateWindowExW(0, L"STATIC", L"Rich preview test", WS_POPUP, 0, 0, 300, 200, + nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); + HWND rich = ks::createRichText(richParent, 1, false); + require(rich != nullptr, "native RichEdit loads without a browser runtime"); + auto styled = ks::textClip(L"plain bold italic under\nnext"); + std::string html = "Version:1.0\r\n
plain bold " + "italic under
next
"; + styled.formats.push_back({0, L"HTML Format", ks::Bytes(html.begin(), html.end())}); + require(ks::setRichText(rich, ks::richPreview(styled)), + "HTML clipboard preview streams into RichEdit"); + auto effects = [&](LONG first, LONG last) + { + CHARRANGE range{first, last}; + SendMessageW(rich, EM_EXSETSEL, 0, (LPARAM)&range); + CHARFORMAT2W format{}; + format.cbSize = sizeof(format); + SendMessageW(rich, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&format); + return format.dwEffects; + }; + require((effects(6, 10) & CFE_BOLD) != 0, "HTML bold is displayed as native bold"); + require((effects(11, 17) & CFE_ITALIC) != 0, "HTML italic is displayed as native italic"); + require((effects(18, 23) & CFE_UNDERLINE) != 0, "HTML underline is displayed natively"); + require((effects(0, 5) & (CFE_BOLD | CFE_ITALIC | CFE_UNDERLINE)) == 0, + "inline formatting does not leak into plain text"); + wchar_t rendered[256]{}; + GetWindowTextW(rich, rendered, 256); + require(std::wstring(rendered).find(L"\r\nnext") != std::wstring::npos || + std::wstring(rendered).find(L"\rnext") != std::wstring::npos, + "HTML line breaks are retained"); + html = "Привіт & 🌍" + ""; + styled.formats.back().data = ks::Bytes(html.begin(), html.end()); + ks::setRichText(rich, ks::richPreview(styled)); + GetWindowTextW(rich, rendered, 256); + require(std::wstring(rendered) == L"Привіт & 🌍", + "HTML entities and Unicode survive without script text"); + require((effects(0, 6) & CFE_BOLD) != 0, "inline CSS bold is rendered"); + auto plain = ks::textClip(L"{\\literal} Привіт"); + ks::setRichText(rich, ks::richPreview(plain)); + GetWindowTextW(rich, rendered, 256); + require(std::wstring(rendered) == plain.text(), "plain Unicode and RTF metacharacters are escaped"); + std::string rtf = "{\\rtf1\\ansi \\b native}"; + plain.formats.push_back({0, L"Rich Text Format", ks::Bytes(rtf.begin(), rtf.end())}); + ks::setRichText(rich, ks::richPreview(plain)); + require((effects(0, 6) & CFE_BOLD) != 0, "original RTF formatting is preserved"); + DestroyWindow(richParent); + ks::Layout en = ks::physicalLayout(); + en.id = L"en"; + ks::Layout ru{nullptr, L"ru", L"Russian", L"ё1234567890-=йцукенгшщзхъфывапролджэячсмитьбю.\\", + L"Ё!\"№;%:?*()_+ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,/"}; + ks::Layout uk{nullptr, L"uk", L"Ukrainian", L"'1234567890-=йцукенгшщзхїфівапролджєячсмитьбю.\\", + L"₴!\"№;%:?*()_+ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,/"}; + require(en.normal.size() == ru.normal.size(), "physical maps align"); + require(en.normal.size() == uk.normal.size(), "Ukrainian maps align"); + require(ks::convert(L"ghbdtn", en, ru) == L"привет", "English to Russian"); + require(ks::convert(L"привіт", uk, en) == L"ghbdsn", "Ukrainian to English"); + require(ks::convert(L"hello привет", en, ru) == L"руддщ привет", "mixed text preserves target words"); + require(ks::convert(L"htdm.dftim", en, ru) == L"ревьюваешь", "punctuation in words"); + std::vector layouts{en, ru, uk}; + require(ks::convertTo(L"ghbdtn", layouts, 1, 0) == L"привет", "hint conversion"); + require(ks::convertTo(L"привет", layouts, 1, 1) == L"привет", "same source is unchanged"); + require(ks::convertTo(L"?", layouts, 1, 0) == L",", "punctuation conversion"); + require(ks::cycleCase(L"hello world") == L"Hello World", "title case"); + require(ks::cycleCase(L"Hello World") == L"HELLO WORLD", "upper case"); + require(ks::cycleCase(L"ПРИВІТ") == L"привіт", "Unicode lower case"); + require(ks::transliterate(L"Привет") == L"Privet", "forward transliteration"); + require(ks::transliterate(L"Київ") == L"Kyiv", "Ukrainian transliteration"); + require(ks::transliterate(L"Shchuka") == L"Щука", "reverse transliteration"); + require(ks::parseTemplate(L"a{cursor}b").suffix == L"b", "snippet suffix"); + require(ks::parseTemplate(L"{{cursor}}").text == L"{cursor}", "escaped cursor"); + require(ks::parseTemplate(L"{cursor}{cursor}").cursors == 2, "duplicate cursor detection"); + require(ks::Hotkey::parse(L"Ctrl+Alt+Shift+Space")->key == VK_SPACE, "hotkey parse"); + require(!ks::Hotkey::parse(L"Ctrl+F25"), "invalid function key"); + require(!ks::Hotkey::parse(L"Ctrl+A+B"), "multiple keys rejected"); + require(ks::Hotkey::parse(L"")->key == 0, "disabled shortcut"); + ks::HotkeyHook hook; + std::vector> bindings{ + {5, {MOD_WIN, 'V'}}, {1, {MOD_ALT, VK_F12}}, {100, {MOD_CONTROL | MOD_SHIFT, 'U'}}}; + require(hook.update('V', true, MOD_WIN, bindings) == 5, "Win+V dispatches configured action"); + require(hook.update('V', true, MOD_WIN, bindings) == -1, "held hotkey does not repeat"); + require(hook.update('V', false, 0, {}) == -1, "key-up consumed after settings change"); + require(hook.update('V', true, MOD_CONTROL, bindings) == 0, "unassigned Ctrl+V unaffected"); + require(hook.update('V', true, MOD_WIN | MOD_SHIFT, bindings) == 0, "modifiers match exactly"); + require(hook.update('V', true, MOD_WIN, {}) == 0, "disabled binding leaves OS shortcut intact"); + require(hook.update(VK_F12, true, MOD_ALT, bindings) == 1, "conversion uses same hook"); + require(hook.update('U', true, MOD_CONTROL | MOD_SHIFT, bindings) == 100, + "direct layout uses same hook"); + require(hook.update(VK_F12, false, 0, bindings) == -1, "independent key release consumed"); + require(hook.update('U', false, 0, bindings) == -1, "second key release consumed"); + require(hook.update('V', true, MOD_WIN, bindings) == 5, "hotkey activates again after release"); + bindings.push_back({2, {MOD_WIN | MOD_SHIFT, 'S'}}); + require(hook.update('S', true, MOD_WIN | MOD_SHIFT, bindings) == 2, + "Win+Shift+S can replace the Snipping Tool shortcut"); + require(hook.update('S', false, 0, bindings) == -1, "Snipping Tool shortcut release consumed"); + for (UINT key : + {UINT('K'), UINT(VK_F12), UINT(VK_SPACE), UINT(VK_OEM_PLUS), UINT(VK_LEFT), UINT(VK_NUMPAD7)}) + { + ks::Hotkey recorded{MOD_CONTROL | MOD_ALT | MOD_SHIFT, key}; + require(ks::Hotkey::parse(recorded.format()) == recorded, "recorded shortcut roundtrip"); + } + ks::Run run; + run.record(L'g', (HWND)1, (HKL)1, 1); + run.record(L'h', (HWND)1, (HKL)1, 2); + require(run.match(L"пр", layouts, en) == 1, "recent input verification"); + require(run.match(L"xx", layouts, en) == -1, "unrelated selected text rejected"); + require(run.match(L"xпр", layouts, en) == -1, + "word selection including older text is rejected before replacement"); + require(run.match(L"р", layouts, en) == -1, + "coalesced Teams selection of only the final character is rejected"); + require(run.match(L"пр ", layouts, en) == -1, + "word selection including unrecorded whitespace is rejected"); + run.record(L'b', (HWND)1, (HKL)1, 2002); + require(run.keys == L"b", "pause resets run"); + run.record(L'c', (HWND)2, (HKL)1, 2003); + require(run.keys == L"c", "window resets run"); + run.record(L'd', (HWND)2, (HKL)2, 2004); + require(run.keys == L"d", "layout resets run"); + run.record(L' ', (HWND)2, (HKL)2, 2005); + run.record(L'e', (HWND)2, (HKL)2, 2006); + require(run.keys == L"e", "word boundary resets run"); + for (int i = 0; i < 513; ++i) + run.record(L'x', (HWND)2, (HKL)2, 2007 + i); + require(run.overflow && run.keys.empty(), "overflow does not select partial input"); + std::map> processes{ + {10, {1, L"ms-teams.exe"}}, {20, {10, L"msedgewebview2.exe"}}, + {30, {20, L"msedgewebview2.exe"}}, {40, {1, L"msedgewebview2.exe"}}, + {50, {10, L"other.exe"}}, {60, {60, L"msedgewebview2.exe"}}}; + auto read = [&](DWORD id) -> std::optional> + { + auto it = processes.find(id); + return it == processes.end() ? std::nullopt : std::optional(it->second); + }; + require(ks::webViewDescendant(30, 10, read), "Teams renderer belongs to host"); + require(!ks::webViewDescendant(40, 10, read), "other WebView rejected"); + require(!ks::webViewDescendant(50, 10, read), "arbitrary child rejected"); + require(!ks::webViewDescendant(60, 10, read), "process cycles rejected"); + require(!ks::webViewDescendant(0, 0, read), "unknown process rejected"); + auto clip = ks::textClip(L"Привіт 🌍\r\nline 2"); + clip.formats.push_back({0, L"HTML Format", {1, 2, 3, 0}}); + auto encoded = ks::encodeClip(clip); + require(ks::decodeClip(encoded).text() == clip.text(), "clipboard Unicode roundtrip"); + require(ks::decodeClip(encoded).formats[1].name == L"HTML Format", "registered format identity"); + auto encrypted = ks::protect(encoded); + require(encrypted != encoded, "history ciphertext differs"); + require(ks::unprotect(encrypted) == encoded, "Windows DPAPI roundtrip"); + encrypted[encrypted.size() / 2] ^= 0x40; + rejects([&] { ks::unprotect(encrypted); }, "tampering rejected"); + encoded.pop_back(); + rejects([&] { ks::decodeClip(encoded); }, "truncated clip rejected"); + auto temp = std::filesystem::temp_directory_path() / + (L"KSwitcher-tests-" + std::to_wstring(GetCurrentProcessId())); + std::filesystem::create_directories(temp); + ks::Pixels pixels{2, 1, {10, 20, 30, 255, 40, 50, 60, 255}}; + ks::Clip firstImage{{ks::imageDib(pixels), {0, L"Ole Private Data", {1, 2, 3}}}}; + auto thumbnail = ks::decodeImage(firstImage, 1, 1); + require(thumbnail.width == 1 && thumbnail.height == 1 && thumbnail.bgra.size() == 4, + "thumbnail decoding limits the resident pixel buffer"); + thumbnail = ks::decodeImage(firstImage, 208, 128); + require(thumbnail.width == 2 && thumbnail.height == 1, + "thumbnail decoding does not upscale small images"); + auto secondImage = firstImage; + secondImage.formats[1].data = {4, 5, 6}; + secondImage.formats.push_back({0, L"DataObject", {9, 8, 7}}); + require(ks::sameClipboardImage(firstImage, secondImage), "OLE metadata does not duplicate an image"); + std::reverse(secondImage.formats.begin(), secondImage.formats.end()); + require(ks::sameClipboardImage(firstImage, secondImage), + "format ordering does not duplicate an image"); + auto recoded = firstImage; + recoded.formats[0].data[24] = 1; // Different bitmap resolution metadata; identical pixels. + require(ks::sameClipboardImage(firstImage, recoded), "decoded image ignores resolution metadata"); + pixels.bgra[0]++; + ks::Clip changedImage{{ks::imageDib(pixels)}}; + require(!ks::sameClipboardImage(firstImage, changedImage), + "a changed pixel remains a distinct image"); + auto withText = firstImage; + withText.formats.push_back(ks::textClip(L"caption").formats.front()); + require(!ks::sameClipboardImage(firstImage, withText), + "image deduplication preserves accompanying text"); + auto withFiles = firstImage; + withFiles.formats.push_back({CF_HDROP, L"", {1}}); + require(!ks::sameClipboardImage(firstImage, withFiles), "image deduplication preserves file copies"); + { + ks::History history(temp / L"images"); + ks::Settings defaults; + history.add(firstImage, L"SnippingTool", defaults); + history.pin(history.entries().front().id); + history.add(secondImage, L"SnippingTool", defaults); + require(history.entries().size() == 1 && history.entries().front().pinned, + "Snipping Tool republish retains one pinned history entry"); + history.add(changedImage, L"SnippingTool", defaults); + require(history.entries().size() == 2, "a new screenshot is retained"); + } + ks::Settings settings; + settings.elevatedAutostart = true; + settings.cycle = {L"en", L"uk"}; + settings.snippets = {{L";address", L"Line 1\r\n{cursor}Line 2"}}; + ks::saveSettings(temp / L"settings.json", settings); + auto restored = ks::loadSettings(temp / L"settings.json", {}); + require(restored.snippets[0].text == settings.snippets[0].text, "settings snippet roundtrip"); + require(restored.cycle == settings.cycle, "layout order roundtrip"); + require(restored.elevatedAutostart, "elevated autostart setting roundtrip"); + require(!ks::loadSettings(temp / L"defaults.json", {}).elevatedAutostart, + "elevated autostart is opt-in for existing and default settings"); + auto invalid = settings; + invalid.undo = invalid.toggle; + rejects([&] { ks::validateSettings(invalid); }, "duplicate shortcut rejected"); + invalid = settings; + invalid.snippets[0].text = L"{cursor}{cursor}"; + rejects([&] { ks::validateSettings(invalid); }, "invalid snippet rejected"); + { + ks::History history(temp / L"history"); + history.add(clip, L"Test", settings); + history.add(clip, L"Test", settings); + require(history.entries().size() == 1, "history consecutive deduplication"); + auto id = history.entries()[0].id; + history.pin(id); + require(history.entries()[0].pinned, "pin persisted in memory"); + require(history.search(L"line 2").size() == 1, "full text search"); + require(history.load(id).text() == clip.text(), "history payload roundtrip"); + } + { + ks::History history(temp / L"history"); + require(history.entries().size() == 1 && history.entries()[0].pinned, "history index reload"); + rejects([&] { history.load(L"../other"); }, "history path traversal rejected"); + history.clear(); + require(history.entries().empty(), "clear history"); + } + // Test-owned directory only; never touch the user's clipboard or stored history. + std::filesystem::remove_all(temp); + CoUninitialize(); + std::cout << "Passed " << passed << " native regression checks\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "FAILED: " << e.what() << '\n'; + return 1; + } +} diff --git a/tools/generate-icon.py b/tools/generate-icon.py new file mode 100644 index 0000000..84e144f --- /dev/null +++ b/tools/generate-icon.py @@ -0,0 +1,23 @@ +"""Regenerate the native multi-resolution icon (development only; requires Pillow).""" +from pathlib import Path +from PIL import Image, ImageDraw + +root = Path(__file__).resolve().parents[1] / 'resources' +# Geometric K: white stem/upper arm, mint lower arm on an indigo keycap. +polygons = [('#FFFFFF', [(8, 7), (12, 7), (12, 25), (8, 25)]), + ('#FFFFFF', [(11, 15), (22, 6), (25, 9), (14, 18)]), + ('#5EEAD4', [(11, 14), (25, 24), (22, 27), (10, 18)])] +svg = '\n' +svg += '\n' +for color, points in polygons: + svg += '\n' +(root / 'kswitcher.svg').write_text(svg + '\n', encoding='utf-8') +scale = 32 +im = Image.new('RGBA', (32*scale, 32*scale)) +draw = ImageDraw.Draw(im) +draw.rounded_rectangle((scale, scale, 31*scale, 31*scale), radius=7*scale, fill='#4F46E5') +for color, points in polygons: + draw.polygon([(x*scale,y*scale) for x,y in points], fill=color) +im = im.resize((256,256),Image.Resampling.LANCZOS) +im.save(root / 'kswitcher.png') +im.save(root / 'kswitcher.ico', sizes=[(s,s) for s in (16,20,24,32,40,48,64,128,256)]) diff --git a/tools/install-elevated-autostart.ps1 b/tools/install-elevated-autostart.ps1 new file mode 100644 index 0000000..6460893 --- /dev/null +++ b/tools/install-elevated-autostart.ps1 @@ -0,0 +1,29 @@ +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot +$resultPath = Join-Path $root 'build\autostart-install-result.json' +try { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + if (-not ([Security.Principal.WindowsPrincipal]::new($identity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Administrator approval is required.' } + $exe = Join-Path $root 'build\KSwitcher.exe' + $staged = Join-Path $root 'build\staged\KSwitcher.exe' + $running = @(Get-Process KSwitcher -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $exe }) + $exitProcess = Start-Process -FilePath $exe -ArgumentList '--exit' -WindowStyle Hidden -PassThru + if (-not $exitProcess.WaitForExit(5000)) { throw 'KSwitcher exit command timed out.' } + foreach ($process in $running) { if (-not $process.WaitForExit(10000)) { throw 'KSwitcher did not stop; no executable was replaced.' } } + Copy-Item -LiteralPath $exe -Destination (Join-Path $root 'build\staged\KSwitcher.previous.exe') -Force + Copy-Item -LiteralPath $staged -Destination $exe -Force + $helper = Start-Process -FilePath $exe -ArgumentList @('--configure-autostart=2', $identity.User.Value) -WindowStyle Hidden -PassThru + $helper.WaitForExit() + if ($helper.ExitCode -ne 0) { throw 'The native autostart helper failed.' } + $settingsPath = Join-Path $root 'build\data\settings.json' + $settings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json + $settings.startWithSystem = $true + $settings | Add-Member -NotePropertyName autostartAsAdministrator -NotePropertyValue $true -Force + [IO.File]::WriteAllText($settingsPath, ($settings | ConvertTo-Json -Depth 12), [Text.UTF8Encoding]::new($false)) + Start-ScheduledTask -TaskName 'KSwitcher' + $task = Get-ScheduledTask -TaskName 'KSwitcher' + [pscustomobject]@{success=$true; task=$task.TaskName; runLevel=[string]$task.Principal.RunLevel; logonType=[string]$task.Principal.LogonType; user=$task.Principal.UserId; executable=$task.Actions.Execute; state=[string]$task.State} | ConvertTo-Json | Set-Content -LiteralPath $resultPath -Encoding UTF8 +} catch { + [pscustomobject]@{success=$false; error=$_.Exception.Message} | ConvertTo-Json | Set-Content -LiteralPath $resultPath -Encoding UTF8 + exit 1 +} diff --git a/tools/update-running-build.ps1 b/tools/update-running-build.ps1 new file mode 100644 index 0000000..c63d8ba --- /dev/null +++ b/tools/update-running-build.ps1 @@ -0,0 +1,12 @@ +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot +try { + $exe = Join-Path $root 'build\KSwitcher.exe' + $running = @(Get-Process KSwitcher -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $exe }) + $stop = Start-Process -FilePath $exe -ArgumentList '--exit' -WindowStyle Hidden -PassThru + if (-not $stop.WaitForExit(5000)) { throw 'Exit command timed out.' } + foreach ($process in $running) { if (-not $process.WaitForExit(10000)) { throw 'KSwitcher did not stop.' } } + Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.exe') -Destination $exe -Force + Start-Process -FilePath $exe -WindowStyle Hidden + 'OK' | Set-Content -LiteralPath (Join-Path $root 'build\update-result.txt') +} catch { $_.Exception.Message | Set-Content -LiteralPath (Join-Path $root 'build\update-result.txt'); exit 1 } diff --git a/vendor/json-LICENSE.txt b/vendor/json-LICENSE.txt new file mode 100644 index 0000000..b5a1027 --- /dev/null +++ b/vendor/json-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2025 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/json.hpp b/vendor/json.hpp new file mode 100644 index 0000000..82d69f7 --- /dev/null +++ b/vendor/json.hpp @@ -0,0 +1,25526 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows calling any std function as if (e.g., with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // char_traits +#include // tuple +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& +is_complete_type < +detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template