diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake index 1670672..87905c0 100644 --- a/cmake/PanoPainterSources.cmake +++ b/cmake/PanoPainterSources.cmake @@ -104,6 +104,8 @@ set(PP_PANOPAINTER_UI_SOURCES set(PP_WINDOWS_PLATFORM_SOURCES src/main.cpp + src/platform_windows/windows_platform_services.cpp + src/platform_windows/windows_platform_services.h ) set(PP_WINDOWS_APP_SOURCES diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 8afc2f6..4f7ade7 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -1,7 +1,7 @@ # Build And Platform Inventory Status: live -Last updated: 2026-06-02 +Last updated: 2026-06-03 This inventory records the known build surfaces during the CMake migration. Keep it updated as platform paths move to shared CMake targets. @@ -10,7 +10,7 @@ Keep it updated as platform paths move to shared CMake targets. | Platform/Target | Current Entrypoint | Notes | | --- | --- | --- | -| Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained for VS 2026 | Raw `.sln/.vcxproj` files removed on 2026-05-31; local machine currently uses Visual Studio 17 2022; `PanoPainter` now links through `pp_platform_windows` and `panopainter_app`, with Windows/vendor link dependencies owned by the platform shell, runtime payload deployment in `cmake/PanoPainterRuntime.cmake`, tested app-level document-open routing plus open/close/save session decisions owned by `pp_app_core`, SDK-free clipboard/cursor/virtual-keyboard/display/share/picker service contracts owned by `pp_platform_api`, and injected `WindowsPlatformServices` owned by `pp_platform_windows`; retained third-party source dependencies contained by `pp_legacy_vendor`, retained asset/file/serialization sources contained by `pp_legacy_assets_io`, retained paint/document/canvas sources contained by `pp_legacy_paint_document`, retained OpenGL runtime sources contained by `pp_legacy_renderer_gl` and folded into `pp_legacy_engine`, retained runtime shell sources contained by `pp_legacy_engine`, retained base UI controls contained by `pp_legacy_ui_core` and folded into `pp_legacy_app`, app orchestration/version metadata owned by `panopainter_app`, and app-specific modal/dialog/panel/canvas workflow nodes owned by `pp_panopainter_ui` | +| Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained for VS 2026 | Raw `.sln/.vcxproj` files removed on 2026-05-31; local machine currently uses Visual Studio 17 2022; `PanoPainter` now links through `pp_platform_windows` and `panopainter_app`, with Windows/vendor link dependencies owned by the platform shell, runtime payload deployment in `cmake/PanoPainterRuntime.cmake`, tested app-level document-open routing plus open/close/save session decisions owned by `pp_app_core`, SDK-free clipboard/cursor/virtual-keyboard/display/share/picker service contracts owned by `pp_platform_api`, and injected `WindowsPlatformServices` now isolated in `src/platform_windows/windows_platform_services.*`; retained third-party source dependencies contained by `pp_legacy_vendor`, retained asset/file/serialization sources contained by `pp_legacy_assets_io`, retained paint/document/canvas sources contained by `pp_legacy_paint_document`, retained OpenGL runtime sources contained by `pp_legacy_renderer_gl` and folded into `pp_legacy_engine`, retained runtime shell sources contained by `pp_legacy_engine`, retained base UI controls contained by `pp_legacy_ui_core` and folded into `pp_legacy_app`, app orchestration/version metadata owned by `panopainter_app`, and app-specific modal/dialog/panel/canvas workflow nodes owned by `pp_panopainter_ui` | | Windows AppX | `PanoPainterPackage/Package.appxmanifest`, `.wapproj` referenced by solution | Distribution packaging | | macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today | | iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today | @@ -447,9 +447,10 @@ Known local toolchain state: - `pp_platform_api` exposes the SDK-free `PlatformServices` interface for clipboard text, cursor visibility, virtual-keyboard visibility, external file display, file sharing, and picker callbacks; Windows live app execution - now uses injected `WindowsPlatformServices` from `pp_platform_windows`, while - non-Windows platforms still reach retained platform bridges through the - debt-tracked legacy adapter in `app_events.cpp`. The iOS/Web + now uses injected `WindowsPlatformServices` from + `src/platform_windows/windows_platform_services.*` in `pp_platform_windows`, + while non-Windows platforms still reach retained platform bridges through + the debt-tracked legacy adapter in `app_events.cpp`. The iOS/Web save-with-writer overload remains a separate app method until export handoff is isolated. - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability, diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 3042cd7..d2493c8 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -35,7 +35,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, and Windows live execution uses injected `WindowsPlatformServices`, but macOS cursor execution still reaches the retained fallback adapter from `App::show_cursor` and `App::hide_cursor` | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, and Windows live execution uses injected `WindowsPlatformServices`, but Apple/Android clipboard execution still reaches retained fallback adapter branches from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by injected `pp_platform_*` services for every supported platform | -| DEBT-0017 | Open | Modernization | `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, and `App::pick_dir` now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `pp_platform_windows`; non-Windows live implementations still use a legacy fallback adapter in `app_events.cpp` that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches; the iOS/Web save-with-writer overload remains separate until export handoff is isolated | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace the `app_events.cpp` legacy adapter with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | +| DEBT-0017 | Open | Modernization | `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, and `App::pick_dir` now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use a legacy fallback adapter in `app_events.cpp` that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches; the iOS/Web save-with-writer overload remains separate until export handoff is isolated | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace the `app_events.cpp` legacy adapter with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index c5e0c43..5e3952a 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -472,9 +472,10 @@ before retained platform clipboard bridges continue. clipboard text, cursor visibility, virtual-keyboard visibility, external file display, file sharing, image/file/save-file pickers, and directory pickers. Windows installs an injected `WindowsPlatformServices` implementation from -`pp_platform_windows`; other platforms still route through the debt-tracked -legacy fallback adapter in `app_events.cpp`, so behavior is preserved while -their platform shell implementations are extracted. The iOS/Web +`src/platform_windows/windows_platform_services.*` in `pp_platform_windows`; +other platforms still route through the debt-tracked legacy fallback adapter +in `app_events.cpp`, so behavior is preserved while their platform shell +implementations are extracted. The iOS/Web save-with-writer overload remains separate because it writes a temporary/exported file before handing control to the platform. `pano_cli plan-cloud-upload` exposes the app-core cloud upload decision used by @@ -992,8 +993,9 @@ Results: visibility dispatch, virtual-keyboard visibility dispatch, external file display dispatch, file sharing dispatch, and picker callback dispatch. The live Windows app now consumes this interface through an injected - `WindowsPlatformServices` instance owned by `pp_platform_windows`; other - platforms still use the legacy fallback adapter. + `WindowsPlatformServices` instance isolated in + `src/platform_windows/windows_platform_services.*`; other platforms still + use the legacy fallback adapter. - `panopainter_validate_shaders` passed, validating 25 shader programs and 7 shader includes for stage markers and include graph integrity. - `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless, diff --git a/src/main.cpp b/src/main.cpp index da47ec5..fd654c2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,11 +5,11 @@ #include "texture.h" #include "image.h" #include "app.h" -#include "platform_api/platform_services.h" #include "canvas.h" #include "keymap.h" #include "hmd.h" #include "renderer_gl/opengl_capabilities.h" +#include "platform_windows/windows_platform_services.h" #include "../resource.h" #include @@ -244,212 +244,6 @@ void win32_update_fps(int frames) PostMessage(hWnd, WM_USER_WAKEUP, 0, 0); } -void win32_show_cursor(bool visible) -{ - std::lock_guard lock(main_task_mutex); - main_tasklist.emplace_back([=] { - if (visible) - while (ShowCursor(true) < 0); - else - while (ShowCursor(false) >= 0); - }); -} - -std::string win32_clipboard_get_text() -{ - std::string ret; - if (OpenClipboard(hWnd)) - { - if (HANDLE h = GetClipboardData(CF_TEXT)) - { - if (char* s = (char*)GlobalLock(h)) - { - ret = s; - GlobalUnlock(h); - } - } - CloseClipboard(); - } - return ret; -} - -bool win32_clipboard_set_text(const std::string& s) -{ - bool success = false; - if (OpenClipboard(hWnd)) - { - // owned by SetClipboardData - if (HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1)) - { - if (char* p = (char*)GlobalLock(h)) - { - std::copy(s.begin(), s.end(), p); - p[s.size()] = 0; // string null-termination - GlobalUnlock(h); - success = true; - } - EmptyClipboard(); - SetClipboardData(CF_TEXT, h); - } - CloseClipboard(); - } - return success; -} - -std::string win32_open_file(const char* filter) -{ - OPENFILENAMEA ofn; - char fileName[MAX_PATH] = ""; - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = hWnd; - ofn.lpstrFilter = filter; - ofn.lpstrFile = fileName; - ofn.nMaxFile = MAX_PATH; - ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; - ofn.lpstrDefExt = ""; - ofn.lpstrInitialDir = ""; - if (GetOpenFileNameA(&ofn) != NULL) - { - return fileName; - } - return ""; -} - -std::string win32_save_file(const char* filter) -{ - OPENFILENAMEA ofn; - char fileName[MAX_PATH] = ""; - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = hWnd; - ofn.lpstrFilter = filter; - ofn.lpstrFile = fileName; - ofn.nMaxFile = MAX_PATH; - ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; - ofn.lpstrDefExt = ""; - ofn.lpstrInitialDir = ""; - if (GetSaveFileNameA(&ofn) != NULL) - { - return fileName; - } - return ""; -} - -std::string win32_open_dir() -{ - BROWSEINFOA bi; - char Buffer[MAX_PATH]; - ZeroMemory(Buffer, MAX_PATH); - ZeroMemory(&bi, sizeof(bi)); - bi.hwndOwner = hWnd; - bi.pszDisplayName = Buffer; - bi.lpszTitle = "Title"; - bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; - LPCITEMIDLIST pFolder = SHBrowseForFolderA(&bi); - if (pFolder == NULL) return ""; - if (!SHGetPathFromIDListA(pFolder, Buffer)) return ""; - return Buffer; -} - -namespace { - -void invoke_selected_path( - const std::string& path, - const pp::platform::PickedPathCallback& callback) -{ - if (!path.empty()) - callback(path); -} - -std::string build_supported_files_filter(const std::vector& types) -{ - std::string filter = "Supported Files ("; - bool first_type = true; - for (const auto& t : types) - { - filter.append(std::string(first_type ? "" : " ,") + "*." + t); - first_type = false; - } - filter.append(")"); - filter.push_back(0); - first_type = true; - for (const auto& t : types) - { - filter.append(std::string(first_type ? "" : ";") + "*." + t); - first_type = false; - } - filter.push_back(0); - return filter; -} - -class WindowsPlatformServices final : public pp::platform::PlatformServices { -public: - [[nodiscard]] std::string clipboard_text() override - { - return win32_clipboard_get_text(); - } - - [[nodiscard]] bool set_clipboard_text(std::string_view text) override - { - return win32_clipboard_set_text(std::string(text)); - } - - void set_cursor_visible(bool visible) override - { - win32_show_cursor(visible); - } - - void set_virtual_keyboard_visible(bool visible) override - { - (void)visible; - } - - void display_file(std::string_view path) override - { - (void)path; - } - - void share_file(std::string_view path) override - { - (void)path; - } - - void pick_image(pp::platform::PickedPathCallback callback) override - { - const std::string path = win32_open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png"); - invoke_selected_path(path, callback); - } - - void pick_file(std::vector file_types, pp::platform::PickedPathCallback callback) override - { - const std::string filter = build_supported_files_filter(file_types); - const std::string path = win32_open_file(filter.c_str()); - invoke_selected_path(path, callback); - } - - void pick_save_file(std::vector file_types, pp::platform::PickedPathCallback callback) override - { - const std::string filter = build_supported_files_filter(file_types); - const std::string path = win32_save_file(filter.c_str()); - invoke_selected_path(path, callback); - } - - void pick_directory(pp::platform::PickedPathCallback callback) override - { - const std::string path = win32_open_dir(); - invoke_selected_path(path, callback); - } -}; - -[[nodiscard]] WindowsPlatformServices& windows_platform_services() -{ - static WindowsPlatformServices services; - return services; -} - -} - int read_WMI_info() { // see: http://win32easy.blogspot.co.uk/2011/03/wmi-in-c-query-everyting-from-your-os.html @@ -940,7 +734,7 @@ int main(int argc, char** argv) PIXELFORMATDESCRIPTOR pfd; App::I = new App(); - App::I->set_platform_services(&windows_platform_services()); + App::I->set_platform_services(&pp::platform::windows::platform_services()); App::I->initLog(); init_shcore_API(); @@ -1268,7 +1062,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) } case WM_ACTIVATE: { - win32_show_cursor(true); + pp::platform::windows::platform_services().set_cursor_visible(true); App::I->ui_task_async([=] { int active = GET_WM_ACTIVATE_STATE(wp, lp); WacomTablet::I.set_focus(active); diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp new file mode 100644 index 0000000..0df5a29 --- /dev/null +++ b/src/platform_windows/windows_platform_services.cpp @@ -0,0 +1,216 @@ +#include "pch.h" +#include "platform_windows/windows_platform_services.h" + +#include + +extern HWND hWnd; +extern std::deque> main_tasklist; +extern std::mutex main_task_mutex; + +namespace { + +void show_cursor(bool visible) +{ + std::lock_guard lock(main_task_mutex); + main_tasklist.emplace_back([=] { + if (visible) + while (ShowCursor(true) < 0); + else + while (ShowCursor(false) >= 0); + }); +} + +std::string clipboard_text() +{ + std::string ret; + if (OpenClipboard(hWnd)) + { + if (HANDLE h = GetClipboardData(CF_TEXT)) + { + if (char* s = static_cast(GlobalLock(h))) + { + ret = s; + GlobalUnlock(h); + } + } + CloseClipboard(); + } + return ret; +} + +bool set_clipboard_text(const std::string& s) +{ + bool success = false; + if (OpenClipboard(hWnd)) + { + // owned by SetClipboardData + if (HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1)) + { + if (char* p = static_cast(GlobalLock(h))) + { + std::copy(s.begin(), s.end(), p); + p[s.size()] = 0; + GlobalUnlock(h); + success = true; + } + EmptyClipboard(); + SetClipboardData(CF_TEXT, h); + } + CloseClipboard(); + } + return success; +} + +std::string open_file(const char* filter) +{ + OPENFILENAMEA ofn; + char fileName[MAX_PATH] = ""; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hWnd; + ofn.lpstrFilter = filter; + ofn.lpstrFile = fileName; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; + ofn.lpstrDefExt = ""; + ofn.lpstrInitialDir = ""; + if (GetOpenFileNameA(&ofn) != NULL) + return fileName; + return ""; +} + +std::string save_file(const char* filter) +{ + OPENFILENAMEA ofn; + char fileName[MAX_PATH] = ""; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hWnd; + ofn.lpstrFilter = filter; + ofn.lpstrFile = fileName; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; + ofn.lpstrDefExt = ""; + ofn.lpstrInitialDir = ""; + if (GetSaveFileNameA(&ofn) != NULL) + return fileName; + return ""; +} + +std::string open_directory() +{ + BROWSEINFOA bi; + char Buffer[MAX_PATH]; + ZeroMemory(Buffer, MAX_PATH); + ZeroMemory(&bi, sizeof(bi)); + bi.hwndOwner = hWnd; + bi.pszDisplayName = Buffer; + bi.lpszTitle = "Title"; + bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; + LPCITEMIDLIST pFolder = SHBrowseForFolderA(&bi); + if (pFolder == NULL) + return ""; + if (!SHGetPathFromIDListA(pFolder, Buffer)) + return ""; + return Buffer; +} + +void invoke_selected_path( + const std::string& path, + const pp::platform::PickedPathCallback& callback) +{ + if (!path.empty()) + callback(path); +} + +std::string build_supported_files_filter(const std::vector& types) +{ + std::string filter = "Supported Files ("; + bool first_type = true; + for (const auto& t : types) + { + filter.append(std::string(first_type ? "" : " ,") + "*." + t); + first_type = false; + } + filter.append(")"); + filter.push_back(0); + first_type = true; + for (const auto& t : types) + { + filter.append(std::string(first_type ? "" : ";") + "*." + t); + first_type = false; + } + filter.push_back(0); + return filter; +} + +class WindowsPlatformServices final : public pp::platform::PlatformServices { +public: + [[nodiscard]] std::string clipboard_text() override + { + return ::clipboard_text(); + } + + [[nodiscard]] bool set_clipboard_text(std::string_view text) override + { + return ::set_clipboard_text(std::string(text)); + } + + void set_cursor_visible(bool visible) override + { + show_cursor(visible); + } + + void set_virtual_keyboard_visible(bool visible) override + { + (void)visible; + } + + void display_file(std::string_view path) override + { + (void)path; + } + + void share_file(std::string_view path) override + { + (void)path; + } + + void pick_image(pp::platform::PickedPathCallback callback) override + { + const std::string path = open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png"); + invoke_selected_path(path, callback); + } + + void pick_file(std::vector file_types, pp::platform::PickedPathCallback callback) override + { + const std::string filter = build_supported_files_filter(file_types); + const std::string path = open_file(filter.c_str()); + invoke_selected_path(path, callback); + } + + void pick_save_file(std::vector file_types, pp::platform::PickedPathCallback callback) override + { + const std::string filter = build_supported_files_filter(file_types); + const std::string path = save_file(filter.c_str()); + invoke_selected_path(path, callback); + } + + void pick_directory(pp::platform::PickedPathCallback callback) override + { + const std::string path = open_directory(); + invoke_selected_path(path, callback); + } +}; + +} + +namespace pp::platform::windows { + +PlatformServices& platform_services() +{ + static WindowsPlatformServices services; + return services; +} + +} diff --git a/src/platform_windows/windows_platform_services.h b/src/platform_windows/windows_platform_services.h new file mode 100644 index 0000000..0dda5a7 --- /dev/null +++ b/src/platform_windows/windows_platform_services.h @@ -0,0 +1,9 @@ +#pragma once + +#include "platform_api/platform_services.h" + +namespace pp::platform::windows { + +[[nodiscard]] PlatformServices& platform_services(); + +}