Own Linux platform services and narrow legacy fallback

This commit is contained in:
2026-06-17 15:32:29 +02:00
parent 3edb6617d0
commit 9750c418bc
9 changed files with 432 additions and 82 deletions

View File

@@ -304,12 +304,18 @@ add_library(pp_platform_linux STATIC
target_include_directories(pp_platform_linux target_include_directories(pp_platform_linux
PUBLIC PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/src") "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(pp_platform_linux
PRIVATE
${PP_LEGACY_INCLUDE_DIRS})
target_link_libraries(pp_platform_linux target_link_libraries(pp_platform_linux
PUBLIC PUBLIC
pp_platform_api pp_platform_api
pp_project_options pp_project_options
PRIVATE PRIVATE
pp_project_warnings) 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 add_library(pp_platform_android STATIC
${PP_PLATFORM_ANDROID_SOURCES}) ${PP_PLATFORM_ANDROID_SOURCES})

View File

@@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions ## 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. - 2026-06-17: `DEBT-0016`/`DEBT-0017` were narrowed again.
`src/platform_android/android_platform_services.*` now owns the concrete `src/platform_android/android_platform_services.*` now owns the concrete
Android `PlatformServices` implementation and Android `PlatformServices` implementation and

View File

@@ -70,6 +70,19 @@ What is already real:
- `pp_app_core` - `pp_app_core`
Latest slice: 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 - `src/platform_android/android_platform_services.*` now owns the concrete
Android `PlatformServices` implementation and `create_platform_services()` Android `PlatformServices` implementation and `create_platform_services()`
instead of leaving the live Android execution surface inside instead of leaving the live Android execution surface inside
@@ -82,8 +95,7 @@ Latest slice:
cross-platform fallback adapter. cross-platform fallback adapter.
- `src/platform_legacy/legacy_platform_services.*` no longer carries the - `src/platform_legacy/legacy_platform_services.*` no longer carries the
Android bridge/configuration surface or the touched Android method branches, Android bridge/configuration surface or the touched Android method branches,
so the retained fallback adapter is narrower again and now focused on the so the retained fallback adapter was narrowed again before the Linux cut.
Linux/Web path plus generic fallback policy.
- `src/platform_apple/apple_platform_services.*` now owns the concrete Apple - `src/platform_apple/apple_platform_services.*` now owns the concrete Apple
`PlatformServices` implementation plus the `create_apple_platform_services()` `PlatformServices` implementation plus the `create_apple_platform_services()`
factory instead of leaving the live Apple execution surface inside factory instead of leaving the live Apple execution surface inside

View File

@@ -52,8 +52,10 @@ Completed, blocked, and superseded task history moved to
`src/platform_apple/apple_platform_services.*`, and the live Apple `src/platform_apple/apple_platform_services.*`, and the live Apple
`PlatformServices` surface now binds directly from those Apple-owned files, `PlatformServices` surface now binds directly from those Apple-owned files,
and Android now also binds directly from and Android now also binds directly from
`src/platform_android/android_platform_services.*`, but the broader `src/platform_android/android_platform_services.*`, and Linux now also
non-Windows fallback adapter still exists for Linux/Web 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 - `platform_legacy` is still part of the live app shell
- The app runtime boundary is not finished: - The app runtime boundary is not finished:
- render/UI queues are static `App` state - 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. the queue is now ordered by code movement instead.
Current slice: 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 - `src/platform_android/android_platform_services.*` now owns the concrete
Android `PlatformServices` implementation and `create_platform_services()` Android `PlatformServices` implementation and `create_platform_services()`
instead of leaving the live Android execution surface in instead of leaving the live Android execution surface in
@@ -93,8 +107,7 @@ Current slice:
cross-platform fallback adapter. cross-platform fallback adapter.
- `src/platform_legacy/legacy_platform_services.*` no longer carries the - `src/platform_legacy/legacy_platform_services.*` no longer carries the
Android bridge/configuration surface or the touched Android method branches, Android bridge/configuration surface or the touched Android method branches,
so the retained fallback adapter is narrower again and now focused on the so the retained fallback adapter was narrowed again before the Linux cut.
Linux/Web path plus generic fallback policy.
- `src/platform_apple/apple_platform_services.*` now owns the concrete Apple - `src/platform_apple/apple_platform_services.*` now owns the concrete Apple
`PlatformServices` implementation plus the `create_apple_platform_services()` `PlatformServices` implementation plus the `create_apple_platform_services()`
factory instead of leaving the live Apple execution surface in factory instead of leaving the live Apple execution surface in

View File

@@ -43,6 +43,7 @@ add_executable(panopainter
../src/app_layout.cpp ../src/app_layout.cpp
../src/app_shaders.cpp ../src/app_shaders.cpp
../src/app_vr.cpp ../src/app_vr.cpp
../src/platform_linux/linux_platform_services.cpp
../src/brush.cpp ../src/brush.cpp
../src/canvas.cpp ../src/canvas.cpp
../src/canvas_layer.cpp ../src/canvas_layer.cpp

View File

@@ -3,42 +3,11 @@
#include <glad/glad.h> #include <glad/glad.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <app.h> #include <app.h>
#include <platform_legacy/legacy_platform_state.h> #include <platform_linux/linux_platform_services.h>
#include <platform_legacy/legacy_platform_services.h>
#include <libgen.h>
#include <pwd.h>
#include <unistd.h>
static App app; static App app;
glm::vec2 g_cursor_pos; 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) void error_log(int code, const char * s)
{ {
printf("glfw error: %s", 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) { pp::platform::linux_desktop::set_fps_title_callback([wnd](std::string title) {
glfwSetWindowTitle(wnd, title.c_str()); glfwSetWindowTitle(wnd, title.c_str());
}); });
auto platform_services = pp::platform::legacy::create_platform_services({ auto platform_services = pp::platform::linux_desktop::create_platform_services({
.glfw_shell = { .acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); },
.acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); }, .present_render_context = [wnd] { glfwSwapBuffers(wnd); },
.present_render_context = [wnd] { glfwSwapBuffers(wnd); }, .request_app_close = [wnd] { glfwSetWindowShouldClose(wnd, GLFW_TRUE); },
.request_app_close = [wnd] { glfwSetWindowShouldClose(wnd, GLFW_TRUE); },
},
}); });
glfwSetCursorPosCallback(wnd, [](GLFWwindow* wnd, double x, double y){ glfwSetCursorPosCallback(wnd, [](GLFWwindow* wnd, double x, double y){

View File

@@ -9,12 +9,7 @@
#include "platform_api/platform_policy.h" #include "platform_api/platform_policy.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#ifdef __LINUX__ #ifdef __WEB__
#include <tinyfiledialogs.h>
#include "platform_linux/linux_platform_services.h"
std::string linux_home_path();
int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE);
#elif __WEB__
void webgl_pick_file(std::function<void(std::string)> callback); void webgl_pick_file(std::function<void(std::string)> callback);
void webgl_pick_file_save( void webgl_pick_file_save(
const std::string& path, const std::string& path,
@@ -37,21 +32,7 @@ public:
[[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override [[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override
{ {
#if __LINUX__ #if __WEB__
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__
const std::string data_path = "/PanoPainter"; const std::string data_path = "/PanoPainter";
mkdir(data_path.c_str(), 0777); mkdir(data_path.c_str(), 0777);
mkdir((data_path + "/brushes").c_str(), 0777); mkdir((data_path + "/brushes").c_str(), 0777);
@@ -111,7 +92,7 @@ public:
void acquire_render_context() override void acquire_render_context() override
{ {
#if __LINUX__ || __WEB__ #if __WEB__
invoke_legacy_glfw_shell_callback(glfw_shell_.acquire_render_context); invoke_legacy_glfw_shell_callback(glfw_shell_.acquire_render_context);
#endif #endif
} }
@@ -122,7 +103,7 @@ public:
void present_render_context() override void present_render_context() override
{ {
#if __LINUX__ || __WEB__ #if __WEB__
invoke_legacy_glfw_shell_callback(glfw_shell_.present_render_context); invoke_legacy_glfw_shell_callback(glfw_shell_.present_render_context);
#endif #endif
} }
@@ -223,19 +204,12 @@ public:
void report_rendered_frames(int frames) override void report_rendered_frames(int frames) override
{ {
#ifdef __LINUX__
pp::platform::linux_desktop::report_rendered_frames(frames);
#else
(void)frames; (void)frames;
#endif
} }
void pick_image(pp::platform::PickedPathCallback callback) override void pick_image(pp::platform::PickedPathCallback callback) override
{ {
#ifdef __LINUX__ #ifdef __WEB__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback); webgl_pick_file(callback);
#else #else
(void)callback; (void)callback;
@@ -244,10 +218,7 @@ public:
void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{ {
#ifdef __LINUX__ #ifdef __WEB__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback); webgl_pick_file(callback);
#else #else
(void)file_types; (void)file_types;
@@ -364,7 +335,7 @@ public:
void request_app_close() override void request_app_close() override
{ {
#ifdef __LINUX__ #if __WEB__
invoke_legacy_glfw_shell_callback(glfw_shell_.request_app_close); invoke_legacy_glfw_shell_callback(glfw_shell_.request_app_close);
#endif #endif
} }

View File

@@ -1,9 +1,358 @@
#include "platform_linux/linux_platform_services.h" #include "platform_linux/linux_platform_services.h"
#ifdef __LINUX__ #include <memory>
#include <string>
#include <utility> #include <utility>
#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 <cerrno>
#include <libgen.h>
#include <pwd.h>
#include <string>
#include <sys/stat.h>
#include <tinyfiledialogs.h>
#include <unistd.h>
#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<std::string> 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<std::string> 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<std::string> 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<void()>& callback)
{
if (callback)
callback();
}
pp::platform::linux_desktop::LinuxPlatformBridge bridge_;
};
}
namespace pp::platform::linux_desktop { namespace pp::platform::linux_desktop {
namespace { namespace {
@@ -19,6 +368,12 @@ void linux_update_fps(int frames)
} }
std::unique_ptr<pp::platform::PlatformServices> create_platform_services(
LinuxPlatformBridge bridge)
{
return std::make_unique<LinuxPlatformServices>(std::move(bridge));
}
void set_fps_title_callback(std::function<void(std::string)> callback) void set_fps_title_callback(std::function<void(std::string)> callback)
{ {
g_fps_title_callback = std::move(callback); g_fps_title_callback = std::move(callback);
@@ -33,6 +388,13 @@ void report_rendered_frames(int frames)
#else #else
namespace pp::platform::linux_desktop { namespace pp::platform::linux_desktop {
std::unique_ptr<pp::platform::PlatformServices> create_platform_services(
LinuxPlatformBridge bridge)
{
(void)bridge;
return nullptr;
}
void set_fps_title_callback(std::function<void(std::string)> callback) void set_fps_title_callback(std::function<void(std::string)> callback)
{ {
(void)callback; (void)callback;

View File

@@ -1,10 +1,22 @@
#pragma once #pragma once
#include "platform_api/platform_services.h"
#include <functional> #include <functional>
#include <memory>
#include <string> #include <string>
namespace pp::platform::linux_desktop { namespace pp::platform::linux_desktop {
struct LinuxPlatformBridge {
std::function<void()> acquire_render_context;
std::function<void()> present_render_context;
std::function<void()> request_app_close;
};
[[nodiscard]] std::unique_ptr<pp::platform::PlatformServices> create_platform_services(
LinuxPlatformBridge bridge = {});
void set_fps_title_callback(std::function<void(std::string)> callback); void set_fps_title_callback(std::function<void(std::string)> callback);
void report_rendered_frames(int frames); void report_rendered_frames(int frames);