From 9750c418bcdcdfd5a14ec675985681c4a79b34c0 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 17 Jun 2026 15:32:29 +0200 Subject: [PATCH] Own Linux platform services and narrow legacy fallback --- CMakeLists.txt | 6 + docs/modernization/debt.md | 6 + docs/modernization/roadmap.md | 16 +- docs/modernization/tasks.md | 21 +- linux/CMakeLists.txt | 1 + linux/src/main.cpp | 43 +- .../legacy_platform_services.cpp | 43 +- .../linux_platform_services.cpp | 366 +++++++++++++++++- src/platform_linux/linux_platform_services.h | 12 + 9 files changed, 432 insertions(+), 82 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68fbcef7..66c3b459 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,12 +304,18 @@ add_library(pp_platform_linux STATIC target_include_directories(pp_platform_linux PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_include_directories(pp_platform_linux + PRIVATE + ${PP_LEGACY_INCLUDE_DIRS}) target_link_libraries(pp_platform_linux PUBLIC pp_platform_api pp_project_options PRIVATE pp_project_warnings) +if(TARGET pp_renderer_gl) + target_link_libraries(pp_platform_linux PUBLIC pp_renderer_gl) +endif() add_library(pp_platform_android STATIC ${PP_PLATFORM_ANDROID_SOURCES}) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 097d18d6..832c4998 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat. ## Reductions +- 2026-06-17: `DEBT-0016`/`DEBT-0017`/`DEBT-0051` were narrowed again. + `src/platform_linux/linux_platform_services.*` now owns the concrete Linux + `PlatformServices` implementation and `linux/src/main.cpp` now binds that + owned service directly, so `src/platform_legacy/legacy_platform_services.*` + no longer carries the touched Linux storage-path, GLFW render-context, + app-close, desktop picker, default-render-target, or FPS-reporting branches. - 2026-06-17: `DEBT-0016`/`DEBT-0017` were narrowed again. `src/platform_android/android_platform_services.*` now owns the concrete Android `PlatformServices` implementation and diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 42536d29..5aa5ad4b 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -70,6 +70,19 @@ What is already real: - `pp_app_core` Latest slice: +- `src/platform_linux/linux_platform_services.*` now owns the concrete Linux + `PlatformServices` implementation and `create_platform_services(...)` + instead of leaving the live Linux execution surface inside + `platform_legacy`. +- `linux/src/main.cpp` now binds that owned Linux `PlatformServices` instance + into `App` directly at the Linux entrypoint. +- The touched Linux storage-path setup, GLFW render-context acquire/present, + app-close dispatch, default render-target binding, desktop file picking, and + FPS reporting now route through `src/platform_linux/linux_platform_services.*` + instead of the cross-platform fallback adapter. +- `src/platform_legacy/legacy_platform_services.*` no longer carries the + touched Linux method branches, so the retained fallback adapter is narrower + again and now focused on the Web path plus generic fallback policy. - `src/platform_android/android_platform_services.*` now owns the concrete Android `PlatformServices` implementation and `create_platform_services()` instead of leaving the live Android execution surface inside @@ -82,8 +95,7 @@ Latest slice: cross-platform fallback adapter. - `src/platform_legacy/legacy_platform_services.*` no longer carries the Android bridge/configuration surface or the touched Android method branches, - so the retained fallback adapter is narrower again and now focused on the - Linux/Web path plus generic fallback policy. + so the retained fallback adapter was narrowed again before the Linux cut. - `src/platform_apple/apple_platform_services.*` now owns the concrete Apple `PlatformServices` implementation plus the `create_apple_platform_services()` factory instead of leaving the live Apple execution surface inside diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 297038b4..004a4b2c 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -52,8 +52,10 @@ Completed, blocked, and superseded task history moved to `src/platform_apple/apple_platform_services.*`, and the live Apple `PlatformServices` surface now binds directly from those Apple-owned files, and Android now also binds directly from - `src/platform_android/android_platform_services.*`, but the broader - non-Windows fallback adapter still exists for Linux/Web + `src/platform_android/android_platform_services.*`, and Linux now also + binds directly from `src/platform_linux/linux_platform_services.*`, but the + broader non-Windows fallback adapter still exists for Web plus generic + fallback policy - `platform_legacy` is still part of the live app shell - The app runtime boundary is not finished: - render/UI queues are static `App` state @@ -81,6 +83,18 @@ Completed, blocked, and superseded task history moved to the queue is now ordered by code movement instead. Current slice: +- `src/platform_linux/linux_platform_services.*` now owns the concrete Linux + `PlatformServices` implementation and `create_platform_services(...)` + instead of leaving the live Linux execution surface in `platform_legacy`. +- `linux/src/main.cpp` now binds that owned Linux `PlatformServices` instance + into `App` directly at the Linux entrypoint. +- The touched Linux storage-path setup, GLFW render-context acquire/present, + app-close dispatch, default render-target binding, desktop file picking, and + FPS reporting now route through `src/platform_linux/linux_platform_services.*` + instead of the cross-platform fallback adapter. +- `src/platform_legacy/legacy_platform_services.*` no longer carries the + touched Linux method branches, so the retained fallback adapter is narrower + again and now focused on the Web path plus generic fallback policy. - `src/platform_android/android_platform_services.*` now owns the concrete Android `PlatformServices` implementation and `create_platform_services()` instead of leaving the live Android execution surface in @@ -93,8 +107,7 @@ Current slice: cross-platform fallback adapter. - `src/platform_legacy/legacy_platform_services.*` no longer carries the Android bridge/configuration surface or the touched Android method branches, - so the retained fallback adapter is narrower again and now focused on the - Linux/Web path plus generic fallback policy. + so the retained fallback adapter was narrowed again before the Linux cut. - `src/platform_apple/apple_platform_services.*` now owns the concrete Apple `PlatformServices` implementation plus the `create_apple_platform_services()` factory instead of leaving the live Apple execution surface in diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index cd347277..d84f2a3e 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(panopainter ../src/app_layout.cpp ../src/app_shaders.cpp ../src/app_vr.cpp + ../src/platform_linux/linux_platform_services.cpp ../src/brush.cpp ../src/canvas.cpp ../src/canvas_layer.cpp diff --git a/linux/src/main.cpp b/linux/src/main.cpp index b226f745..b54ff442 100644 --- a/linux/src/main.cpp +++ b/linux/src/main.cpp @@ -3,42 +3,11 @@ #include #include #include -#include -#include -#include -#include -#include +#include static App app; glm::vec2 g_cursor_pos; -int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE) -{ - struct stat sb; - - if (dir.empty()) { - errno = EINVAL; - return 1; - } - - if (!stat(dir.c_str(), &sb)) - return 0; - - mkpath(dirname(strdupa(dir.c_str())), mode); - - int ret = mkdir(dir.c_str(), mode); - chmod(dir.c_str(), S_IRWXU); - if (ret != 0) - LOG("mkdir failed with error %d on %s", errno, dir.c_str()); - return ret; -} - -std::string linux_home_path() -{ - struct passwd *pw = getpwuid(getuid()); - return pw->pw_dir; -} - void error_log(int code, const char * s) { printf("glfw error: %s", s); @@ -67,12 +36,10 @@ int main(int argc, char** args) pp::platform::linux_desktop::set_fps_title_callback([wnd](std::string title) { glfwSetWindowTitle(wnd, title.c_str()); }); - auto platform_services = pp::platform::legacy::create_platform_services({ - .glfw_shell = { - .acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); }, - .present_render_context = [wnd] { glfwSwapBuffers(wnd); }, - .request_app_close = [wnd] { glfwSetWindowShouldClose(wnd, GLFW_TRUE); }, - }, + auto platform_services = pp::platform::linux_desktop::create_platform_services({ + .acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); }, + .present_render_context = [wnd] { glfwSwapBuffers(wnd); }, + .request_app_close = [wnd] { glfwSetWindowShouldClose(wnd, GLFW_TRUE); }, }); glfwSetCursorPosCallback(wnd, [](GLFWwindow* wnd, double x, double y){ diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp index 6b20fe07..ed7ed2fe 100644 --- a/src/platform_legacy/legacy_platform_services.cpp +++ b/src/platform_legacy/legacy_platform_services.cpp @@ -9,12 +9,7 @@ #include "platform_api/platform_policy.h" #include "renderer_gl/opengl_capabilities.h" -#ifdef __LINUX__ -#include -#include "platform_linux/linux_platform_services.h" -std::string linux_home_path(); -int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE); -#elif __WEB__ +#ifdef __WEB__ void webgl_pick_file(std::function callback); void webgl_pick_file_save( const std::string& path, @@ -37,21 +32,7 @@ public: [[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override { -#if __LINUX__ - const std::string data_path = linux_home_path() + "/PanoPainter"; - mkpath(data_path + "/brushes"); - mkpath(data_path + "/brushes/thumbs"); - mkpath(data_path + "/patterns"); - mkpath(data_path + "/patterns/thumbs"); - mkpath(data_path + "/settings"); - mkpath(data_path + "/frames"); - return { - data_path, - data_path, - data_path + "/frames", - {}, - }; -#elif __WEB__ +#if __WEB__ const std::string data_path = "/PanoPainter"; mkdir(data_path.c_str(), 0777); mkdir((data_path + "/brushes").c_str(), 0777); @@ -111,7 +92,7 @@ public: void acquire_render_context() override { -#if __LINUX__ || __WEB__ +#if __WEB__ invoke_legacy_glfw_shell_callback(glfw_shell_.acquire_render_context); #endif } @@ -122,7 +103,7 @@ public: void present_render_context() override { -#if __LINUX__ || __WEB__ +#if __WEB__ invoke_legacy_glfw_shell_callback(glfw_shell_.present_render_context); #endif } @@ -223,19 +204,12 @@ public: void report_rendered_frames(int frames) override { -#ifdef __LINUX__ - pp::platform::linux_desktop::report_rendered_frames(frames); -#else (void)frames; -#endif } void pick_image(pp::platform::PickedPathCallback callback) override { -#ifdef __LINUX__ - if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false)) - invoke_picked_path_if_selected(p, callback); -#elif __WEB__ +#ifdef __WEB__ webgl_pick_file(callback); #else (void)callback; @@ -244,10 +218,7 @@ public: void pick_file(std::vector file_types, pp::platform::PickedPathCallback callback) override { -#ifdef __LINUX__ - if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false)) - invoke_picked_path_if_selected(p, callback); -#elif __WEB__ +#ifdef __WEB__ webgl_pick_file(callback); #else (void)file_types; @@ -364,7 +335,7 @@ public: void request_app_close() override { -#ifdef __LINUX__ +#if __WEB__ invoke_legacy_glfw_shell_callback(glfw_shell_.request_app_close); #endif } diff --git a/src/platform_linux/linux_platform_services.cpp b/src/platform_linux/linux_platform_services.cpp index 3b06fc9e..7dba7a65 100644 --- a/src/platform_linux/linux_platform_services.cpp +++ b/src/platform_linux/linux_platform_services.cpp @@ -1,9 +1,358 @@ #include "platform_linux/linux_platform_services.h" -#ifdef __LINUX__ -#include +#include #include +#ifdef __LINUX__ +#include "legacy_ui_gl_dispatch.h" +#include "log.h" +#include "platform_api/network_tls_policy.h" +#include "platform_api/platform_policy.h" +#include "renderer_gl/opengl_capabilities.h" + +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef __LINUX__ +namespace { + +constexpr auto kPlatformFamily = pp::platform::PlatformFamily::linux_desktop; + +std::string linux_home_path() +{ + struct passwd* pw = getpwuid(getuid()); + return pw->pw_dir; +} + +int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE) +{ + struct stat sb; + + if (dir.empty()) + { + errno = EINVAL; + return 1; + } + + if (!stat(dir.c_str(), &sb)) + return 0; + + mkpath(dirname(strdupa(dir.c_str())), mode); + + int ret = mkdir(dir.c_str(), mode); + chmod(dir.c_str(), S_IRWXU); + if (ret != 0) + LOG("mkdir failed with error %d on %s", errno, dir.c_str()); + return ret; +} + +void invoke_picked_path_if_selected( + const char* path, + const pp::platform::PickedPathCallback& callback) +{ + if (!path || !callback) + return; + callback(path); +} + +class LinuxPlatformServices final : public pp::platform::PlatformServices { +public: + explicit LinuxPlatformServices(pp::platform::linux_desktop::LinuxPlatformBridge bridge) + : bridge_(std::move(bridge)) + { + } + + [[nodiscard]] PlatformStoragePaths prepare_storage_paths() override + { + const std::string data_path = linux_home_path() + "/PanoPainter"; + mkpath(data_path + "/brushes"); + mkpath(data_path + "/brushes/thumbs"); + mkpath(data_path + "/patterns"); + mkpath(data_path + "/patterns/thumbs"); + mkpath(data_path + "/settings"); + mkpath(data_path + "/frames"); + return { + data_path, + data_path, + data_path + "/frames", + {}, + }; + } + + void log_stacktrace() override + { + } + + void trigger_crash_test() override + { + } + + [[nodiscard]] std::string clipboard_text() override + { + return {}; + } + + [[nodiscard]] bool set_clipboard_text(std::string_view text) override + { + (void)text; + return false; + } + + void set_cursor_visible(bool visible) override + { + (void)visible; + } + + void set_virtual_keyboard_visible(bool visible) override + { + (void)visible; + } + + void attach_ui_thread() override + { + } + + void detach_ui_thread() override + { + } + + void acquire_render_context() override + { + invoke_shell_callback(bridge_.acquire_render_context); + } + + void release_render_context() override + { + } + + void present_render_context() override + { + invoke_shell_callback(bridge_.present_render_context); + } + + void bind_default_render_target() override + { + pp::legacy::ui_gl::bind_opengl_framebuffer( + pp::renderer::gl::framebuffer_target(), + pp::renderer::gl::default_framebuffer_id()); + } + + void bind_main_render_target() override + { + bind_default_render_target(); + } + + void apply_render_platform_hints() override + { + } + + void install_render_debug_callback() override + { + } + + void begin_render_capture_frame() override + { + } + + void end_render_capture_frame() override + { + } + + [[nodiscard]] bool deletes_recorded_files_on_clear() override + { + return platform_deletes_recorded_files_on_clear(kPlatformFamily); + } + + void clear_recorded_files(std::string_view recording_path) override + { + (void)recording_path; + } + + void publish_exported_image(std::string_view path) override + { + (void)path; + } + + void flush_persistent_storage() override + { + } + + [[nodiscard]] std::vector document_browse_roots( + std::string_view work_path, + std::string_view data_path) override + { + return platform_document_browse_roots(kPlatformFamily, work_path, data_path); + } + + void save_ui_state() override + { + } + + [[nodiscard]] bool enables_live_asset_reloading() override + { + return platform_enables_live_asset_reloading(kPlatformFamily); + } + + void update_platform_frame(float delta_time_seconds) override + { + (void)delta_time_seconds; + } + + void report_rendered_frames(int frames) override + { + pp::platform::linux_desktop::report_rendered_frames(frames); + } + + void display_file(std::string_view path) override + { + (void)path; + } + + void share_file(std::string_view path) override + { + (void)path; + } + + void request_app_close() override + { + invoke_shell_callback(bridge_.request_app_close); + } + + [[nodiscard]] bool start_vr_mode() override + { + return false; + } + + void stop_vr_mode() override + { + } + + void pick_image(PickedPathCallback callback) override + { + if (auto* path = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false)) + invoke_picked_path_if_selected(path, callback); + } + + void pick_file(std::vector file_types, PickedPathCallback callback) override + { + (void)file_types; + if (auto* path = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false)) + invoke_picked_path_if_selected(path, callback); + } + + void pick_save_file(std::vector file_types, PickedPathCallback callback) override + { + (void)file_types; + (void)callback; + } + + void pick_directory(PickedPathCallback callback) override + { + (void)callback; + } + + [[nodiscard]] bool supports_working_directory_picker() override + { + return platform_supports_working_directory_picker(kPlatformFamily); + } + + [[nodiscard]] std::string format_working_directory_path(std::string_view path) override + { + return std::string(path); + } + + [[nodiscard]] bool uses_prepared_file_writes() override + { + return platform_uses_prepared_file_writes(kPlatformFamily); + } + + [[nodiscard]] bool uses_work_directory_document_export_collections() override + { + return platform_uses_work_directory_document_export_collections(kPlatformFamily); + } + + [[nodiscard]] bool disables_network_tls_verification() override + { + return default_disables_network_tls_verification(); + } + + [[nodiscard]] bool uses_ppbr_export_data_directory_override() override + { + return platform_uses_ppbr_export_data_directory_override(kPlatformFamily); + } + + [[nodiscard]] bool supports_sonarpen() override + { + return platform_supports_sonarpen(kPlatformFamily); + } + + void start_sonarpen() override + { + } + + [[nodiscard]] int default_canvas_resolution() override + { + return platform_default_canvas_resolution(kPlatformFamily); + } + + [[nodiscard]] bool draws_canvas_tip_for_pointer( + bool is_mouse, + bool is_stylus, + bool is_left_button_release) override + { + return platform_draws_canvas_tip_for_pointer( + kPlatformFamily, + is_mouse, + is_stylus, + is_left_button_release); + } + + [[nodiscard]] float adjust_canvas_input_pressure(float pressure) override + { + return pressure; + } + + [[nodiscard]] PreparedFileTarget prepare_writable_file( + std::string_view type, + std::string_view default_name, + std::string_view data_path, + std::string_view temporary_path) override + { + return plan_platform_writable_file( + kPlatformFamily, + type, + default_name, + data_path, + temporary_path); + } + + void save_prepared_file( + std::string_view path, + std::string_view suggested_name, + PreparedFileCallback callback) override + { + (void)suggested_name; + callback(std::string(path), false); + } + +private: + static void invoke_shell_callback(const std::function& callback) + { + if (callback) + callback(); + } + + pp::platform::linux_desktop::LinuxPlatformBridge bridge_; +}; + +} + namespace pp::platform::linux_desktop { namespace { @@ -19,6 +368,12 @@ void linux_update_fps(int frames) } +std::unique_ptr create_platform_services( + LinuxPlatformBridge bridge) +{ + return std::make_unique(std::move(bridge)); +} + void set_fps_title_callback(std::function callback) { g_fps_title_callback = std::move(callback); @@ -33,6 +388,13 @@ void report_rendered_frames(int frames) #else namespace pp::platform::linux_desktop { +std::unique_ptr create_platform_services( + LinuxPlatformBridge bridge) +{ + (void)bridge; + return nullptr; +} + void set_fps_title_callback(std::function callback) { (void)callback; diff --git a/src/platform_linux/linux_platform_services.h b/src/platform_linux/linux_platform_services.h index 6edb1514..a4d4b43e 100644 --- a/src/platform_linux/linux_platform_services.h +++ b/src/platform_linux/linux_platform_services.h @@ -1,10 +1,22 @@ #pragma once +#include "platform_api/platform_services.h" + #include +#include #include namespace pp::platform::linux_desktop { +struct LinuxPlatformBridge { + std::function acquire_render_context; + std::function present_render_context; + std::function request_app_close; +}; + +[[nodiscard]] std::unique_ptr create_platform_services( + LinuxPlatformBridge bridge = {}); + void set_fps_title_callback(std::function callback); void report_rendered_frames(int frames);