From 9f1555d6e032cd0cb84041d16ef91f1a1e4fc81e Mon Sep 17 00:00:00 2001 From: Mars Ultor Date: Mon, 22 Dec 2025 14:01:27 -0600 Subject: [PATCH] we wrote a single frame. yipeeeee --- src/main.cpp | 440 ++++++++++++++++++++++++++++++++------------------ src/state.cpp | 12 ++ src/state.hpp | 12 ++ 3 files changed, 311 insertions(+), 153 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 46dc79e..2323bf5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,214 +1,348 @@ #include "spdlog/spdlog.h" + #include +#include #include #include +#include + +#include + #include #include #include + #include "state.hpp" -static void output_handle_geometry(void* data, wl_output* output, - int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, - int32_t subpixel, const char* make, const char* model, int32_t transform) -{ - auto& out = *static_cast(data); - spdlog::debug("Output: {} {}", make, model); -} +// ============================================================================ +// EGL setup +// ============================================================================ -static void output_handle_mode(void* data, wl_output* output, - uint32_t flags, int32_t width, int32_t height, int32_t refresh) -{ - auto& out = *static_cast(data); - if (flags & WL_OUTPUT_MODE_CURRENT) { - out.width = static_cast(width); - out.height = static_cast(height); - spdlog::info("Output mode: {}x{} @ {}Hz", width, height, refresh / 1000); +void init_egl(state::LockScreenState& ctx) { + ctx.egl_display = eglGetDisplay((EGLNativeDisplayType)ctx.display); + if (ctx.egl_display == EGL_NO_DISPLAY) { + throw std::runtime_error("Failed to get EGL display"); } + + EGLint major = 0, minor = 0; + if (!eglInitialize(ctx.egl_display, &major, &minor)) { + throw std::runtime_error("Failed to initialize EGL"); + } + + spdlog::info("EGL version {}.{}", major, minor); + + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + throw std::runtime_error("Failed to bind OpenGL ES API"); + } + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + + EGLint num_configs = 0; + if (!eglChooseConfig(ctx.egl_display, + config_attribs, + &ctx.egl_config, + 1, + &num_configs) || + num_configs == 0) { + throw std::runtime_error("Failed to choose EGL config"); + } + + spdlog::info("EGL initialized successfully"); } -static void output_handle_done(void* data, wl_output* output) {} -static void output_handle_scale(void* data, wl_output* output, int32_t factor) {} +void create_egl_surface_for_output(state::LockScreenState& ctx, + state::Output& out, + bool load_glad) { + if (!out.configured) { + throw std::runtime_error("Output not configured"); + } + + out.window = wl_egl_window_create(out.wl_out_surface, + out.surface_width, + out.surface_height); + if (!out.window) { + throw std::runtime_error("Failed to create wl_egl_window"); + } + + out.egl_surface = eglCreateWindowSurface( + ctx.egl_display, + ctx.egl_config, + (EGLNativeWindowType)out.window, + nullptr); + + if (out.egl_surface == EGL_NO_SURFACE) { + throw std::runtime_error("Failed to create EGL surface"); + } + + const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + out.egl_context = eglCreateContext(ctx.egl_display, + ctx.egl_config, + EGL_NO_CONTEXT, + context_attribs); + + if (out.egl_context == EGL_NO_CONTEXT) { + throw std::runtime_error("Failed to create EGL context"); + } + + if (!eglMakeCurrent(ctx.egl_display, + out.egl_surface, + out.egl_surface, + out.egl_context)) { + throw std::runtime_error("eglMakeCurrent failed"); + } + + if (load_glad) { + if (!gladLoadGLES2((GLADloadfunc)eglGetProcAddress)) { + throw std::runtime_error("Failed to load GLAD"); + } + + spdlog::info("GL_VENDOR = {}", + reinterpret_cast(glGetString(GL_VENDOR))); + spdlog::info("GL_RENDERER = {}", + reinterpret_cast(glGetString(GL_RENDERER))); + spdlog::info("GL_VERSION = {}", + reinterpret_cast(glGetString(GL_VERSION))); + + } + + spdlog::info("EGL surface created for output {} ({}x{})", + out.registry_id, + out.surface_width, + out.surface_height); +} + +// ============================================================================ +// Wayland listeners +// ============================================================================ + +static void output_handle_geometry(void*, wl_output*, int32_t, int32_t, + int32_t, int32_t, int32_t, + const char*, const char*, int32_t) {} + +static void output_handle_mode(void* data, + wl_output*, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) { + if (!(flags & WL_OUTPUT_MODE_CURRENT)) { + return; + } + + auto& out = *static_cast(data); + out.width = static_cast(width); + out.height = static_cast(height); + + spdlog::info("Output mode {}x{} @ {}Hz", + width, + height, + refresh / 1000); +} + +static void output_handle_done(void*, wl_output*) {} +static void output_handle_scale(void*, wl_output*, int32_t) {} static const wl_output_listener output_listener = { .geometry = output_handle_geometry, - .mode = output_handle_mode, - .done = output_handle_done, - .scale = output_handle_scale, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, }; -// Registry callbacks - discover globals -static void registry_handle_global( - void* data, - wl_registry* registry, - uint32_t name, - const char* interface, - uint32_t version) -{ +static void registry_handle_global(void* data, + wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t) { auto& ctx = *static_cast(data); - + if (strcmp(interface, wl_compositor_interface.name) == 0) { ctx.compostier = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, 6) - ); - spdlog::info("Bound to wl_compositor"); - } - else if (strcmp(interface, wl_output_interface.name) == 0) { - auto output = std::make_shared(); - output->output = static_cast( - wl_registry_bind(registry, name, &wl_output_interface, 4) - ); - output->registry_id = name; - - ctx.outputs.push_back(output); - - // Pass raw pointer to the Output object - wl_output_add_listener(output->output, &output_listener, output.get()); - - spdlog::info("Discovered output (registry_id={})", name); - } - else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) { - ctx.lock_manager = static_cast( - wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, 1) - ); - spdlog::info("Bound to ext_session_lock_manager_v1"); + wl_registry_bind(registry, + name, + &wl_compositor_interface, + 6)); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + auto out = std::make_shared(); + out->output = static_cast( + wl_registry_bind(registry, + name, + &wl_output_interface, + 4)); + out->registry_id = name; + + wl_output_add_listener(out->output, + &output_listener, + out.get()); + + ctx.outputs.push_back(out); + } else if (strcmp(interface, + ext_session_lock_manager_v1_interface.name) == 0) { + ctx.lock_manager = + static_cast( + wl_registry_bind(registry, + name, + &ext_session_lock_manager_v1_interface, + 1)); } } -static void registry_handle_global_remove(void* data, wl_registry* registry, uint32_t name) { - auto& ctx = *static_cast(data); - - spdlog::debug("Global removed: {}", name); - - // Remove output if it matches - ctx.outputs.erase( - std::remove_if(ctx.outputs.begin(), ctx.outputs.end(), - [name](const auto& out) { return out->registry_id == name; }), - ctx.outputs.end() - ); -} +static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {} static const wl_registry_listener registry_listener = { - .global = registry_handle_global, + .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; - -static void lock_handle_locked(void* data, ext_session_lock_v1* lock) { +static void lock_handle_locked(void* data, ext_session_lock_v1*) { auto& ctx = *static_cast(data); ctx.locked = true; - spdlog::info("🔒 Session LOCKED! We now control all input"); + spdlog::info("🔒 Session locked"); } -static void lock_handle_finished(void* data, ext_session_lock_v1* lock) { +static void lock_handle_finished(void* data, ext_session_lock_v1*) { auto& ctx = *static_cast(data); - spdlog::error("Lock finished/denied by compositor - exiting"); ctx.running = false; + spdlog::error("Session lock denied/finished"); } static const ext_session_lock_v1_listener lock_listener = { - .locked = lock_handle_locked, + .locked = lock_handle_locked, .finished = lock_handle_finished, }; -static void lock_surface_handle_configure( - void* data, - ext_session_lock_surface_v1* lock_surface, - uint32_t serial, - uint32_t width, - uint32_t height) -{ +static void lock_surface_handle_configure(void* data, + ext_session_lock_surface_v1* surface, + uint32_t serial, + uint32_t width, + uint32_t height) { auto& out = *static_cast(data); - - // MUST acknowledge the configure - ext_session_lock_surface_v1_ack_configure(lock_surface, serial); - - out.surface_width = width; + + ext_session_lock_surface_v1_ack_configure(surface, serial); + + out.surface_width = width; out.surface_height = height; - out.configured = true; - - spdlog::info("Lock surface configured: {}x{}", width, height); - - // TODO: Create EGL surface here with these dimensions - // TODO: Render first frame - - // For now, just commit an empty surface - wl_surface_commit(out.wl_out_surface); + out.configured = true; + + spdlog::info("Lock surface configured {}x{}", width, height); } static const ext_session_lock_surface_v1_listener lock_surface_listener = { .configure = lock_surface_handle_configure, }; +// ============================================================================ +// Main +// ============================================================================ -int main (int argc, char *argv[]) { - std::shared_ptr application_state = std::make_shared(); - application_state->display = wl_display_connect(nullptr); +int main() { + auto ctx = std::make_shared(); - if(!application_state->display) throw std::runtime_error("failed to initialize display"); - spdlog::debug("Successfully initialized a display"); - - application_state->compositer_registry = wl_display_get_registry(application_state->display); - - application_state->compositer_registry = wl_display_get_registry(application_state->display); - wl_registry_add_listener(application_state->compositer_registry, ®istry_listener, application_state.get()); - - // Process all registry events - wl_display_roundtrip(application_state->display); - - // Validate we got what we need - if (!application_state->compostier) { - throw std::runtime_error("Compositor doesn't support wl_compositor"); + ctx->display = wl_display_connect(nullptr); + if (!ctx->display) { + throw std::runtime_error("Failed to connect to Wayland"); } - if (!application_state->lock_manager) { - throw std::runtime_error("Compositor doesn't support ext_session_lock_manager_v1"); + + wl_registry* registry = wl_display_get_registry(ctx->display); + wl_registry_add_listener(registry, + ®istry_listener, + ctx.get()); + wl_display_roundtrip(ctx->display); + + if (!ctx->compostier || !ctx->lock_manager || ctx->outputs.empty()) { + throw std::runtime_error("Required Wayland globals missing"); } - if (application_state->outputs.empty()) { - throw std::runtime_error("No outputs found"); + + ctx->session_lock = + ext_session_lock_manager_v1_lock(ctx->lock_manager); + ext_session_lock_v1_add_listener(ctx->session_lock, + &lock_listener, + ctx.get()); + + for (auto& out : ctx->outputs) { + out->wl_out_surface = + wl_compositor_create_surface(ctx->compostier); + + out->lock_surface = + ext_session_lock_v1_get_lock_surface( + ctx->session_lock, + out->wl_out_surface, + out->output); + + ext_session_lock_surface_v1_add_listener( + out->lock_surface, + &lock_surface_listener, + out.get()); + + wl_surface_commit(out->wl_out_surface); } - - spdlog::info("Found {} output(s)", application_state->outputs.size()); - - spdlog::info("Found {} output(s)", application_state->outputs.size()); -// Request lock session - spdlog::info("Requesting session lock..."); - application_state->session_lock = ext_session_lock_manager_v1_lock(application_state->lock_manager); - ext_session_lock_v1_add_listener(application_state->session_lock, &lock_listener, application_state.get()); + wl_display_roundtrip(ctx->display); -// Create lock surface for each output - for (auto& output : application_state->outputs) { - // Create Wayland surface - output->wl_out_surface= wl_compositor_create_surface(application_state->compostier); - - // Get lock surface for this output - output->lock_surface = ext_session_lock_v1_get_lock_surface( - application_state->session_lock, - output->wl_out_surface, - output->output - ); - - // Add listener for configure events - ext_session_lock_surface_v1_add_listener( - output->lock_surface, - &lock_surface_listener, - output.get() - ); - - // Commit to trigger configure - wl_surface_commit(output->wl_out_surface); - - spdlog::debug("Created lock surface for output {}", output->registry_id); - } + init_egl(*ctx); - // Flush and wait for configure/locked events - wl_display_flush(application_state->display); - wl_display_roundtrip(application_state->display); + bool glad_loaded = false; - if (!application_state->locked) { - throw std::runtime_error("Failed to acquire lock"); - } + for (auto& out : ctx->outputs) { + create_egl_surface_for_output(*ctx, + *out, + !glad_loaded); + glad_loaded = true; - spdlog::info("✅ Lock acquired successfully!"); + glViewport(0, 0, + out->surface_width, + out->surface_height); + glClearColor(0.1f, 0.1f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); - return 0; + eglSwapBuffers(ctx->egl_display, out->egl_surface); + } + + wl_display_roundtrip(ctx->display); + + // ====================== + // CLEAN SHUTDOWN + // ====================== + + for (auto& out : ctx->outputs) { + if (out->egl_context != EGL_NO_CONTEXT) { + eglDestroyContext(ctx->egl_display, out->egl_context); + } + if (out->egl_surface != EGL_NO_SURFACE) { + eglDestroySurface(ctx->egl_display, out->egl_surface); + } + if (out->window) { + wl_egl_window_destroy(out->window); + } + if (out->lock_surface) { + ext_session_lock_surface_v1_destroy(out->lock_surface); + } + if (out->wl_out_surface) { + wl_surface_destroy(out->wl_out_surface); + } + } + + if (ctx->session_lock) { + ext_session_lock_v1_destroy(ctx->session_lock); + } + + eglTerminate(ctx->egl_display); + wl_display_disconnect(ctx->display); + + spdlog::info("Exited cleanly after one frame"); + + return 0; } + diff --git a/src/state.cpp b/src/state.cpp index 2bbaf9a..9967b9c 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -10,6 +10,10 @@ namespace state{ // IMPORTANT: Clear outputs BEFORE destroying display // This ensures Output destructors run while display is still valid outputs.clear(); + if (egl_display != EGL_NO_DISPLAY) { + eglTerminate(egl_display); + egl_display = EGL_NO_DISPLAY; + } if (lock_manager) { ext_session_lock_manager_v1_destroy(lock_manager); lock_manager = nullptr; @@ -35,6 +39,14 @@ namespace state{ } Output::~Output() { + if (egl_context != EGL_NO_CONTEXT) { + eglDestroyContext(eglGetCurrentDisplay(), egl_context); + egl_context = EGL_NO_CONTEXT; + } + if (egl_surface != EGL_NO_SURFACE) { + eglDestroySurface(eglGetCurrentDisplay(), egl_surface); + egl_surface = EGL_NO_SURFACE; + } if (output) { wl_output_destroy(output); output = nullptr; diff --git a/src/state.hpp b/src/state.hpp index b40ede9..28b6f34 100644 --- a/src/state.hpp +++ b/src/state.hpp @@ -9,6 +9,9 @@ #include #include #include "ext-session-lock-v1-client-protocol.h" +#include +#include + namespace state{ struct Output{ wl_output* output = nullptr; @@ -21,6 +24,12 @@ namespace state{ bool configured = false; uint32_t surface_width = uint32_t {0}; uint32_t surface_height = uint32_t {0}; + + wl_egl_window* window = nullptr; + EGLSurface egl_surface = EGL_NO_SURFACE; + EGLContext egl_context = EGL_NO_CONTEXT; + + ~Output(); }; struct LockScreenState{ @@ -38,6 +47,9 @@ namespace state{ ext_session_lock_v1* session_lock = nullptr; bool locked=false; + EGLDisplay egl_display = EGL_NO_DISPLAY; + EGLConfig egl_config = nullptr; + ~LockScreenState(); }; }