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
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})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -3,42 +3,11 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <app.h>
#include <platform_legacy/legacy_platform_state.h>
#include <platform_legacy/legacy_platform_services.h>
#include <libgen.h>
#include <pwd.h>
#include <unistd.h>
#include <platform_linux/linux_platform_services.h>
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){

View File

@@ -9,12 +9,7 @@
#include "platform_api/platform_policy.h"
#include "renderer_gl/opengl_capabilities.h"
#ifdef __LINUX__
#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__
#ifdef __WEB__
void webgl_pick_file(std::function<void(std::string)> 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<std::string> 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
}

View File

@@ -1,9 +1,358 @@
#include "platform_linux/linux_platform_services.h"
#ifdef __LINUX__
#include <string>
#include <memory>
#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 {
@@ -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)
{
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<pp::platform::PlatformServices> create_platform_services(
LinuxPlatformBridge bridge)
{
(void)bridge;
return nullptr;
}
void set_fps_title_callback(std::function<void(std::string)> callback)
{
(void)callback;

View File

@@ -1,10 +1,22 @@
#pragma once
#include "platform_api/platform_services.h"
#include <functional>
#include <memory>
#include <string>
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 report_rendered_frames(int frames);