we wrote a single frame. yipeeeee

This commit is contained in:
2025-12-22 14:01:27 -06:00
parent 2525088dea
commit 9f1555d6e0
3 changed files with 311 additions and 153 deletions

View File

@@ -1,33 +1,158 @@
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include <cstdint> #include <cstdint>
#include <cstring>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <vector>
#include <glad/gles2.h>
#include <wayland-client-core.h> #include <wayland-client-core.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <wayland-egl-core.h> #include <wayland-egl-core.h>
#include "state.hpp" #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, // EGL setup
int32_t subpixel, const char* make, const char* model, int32_t transform) // ============================================================================
{
auto& out = *static_cast<state::Output*>(data); void init_egl(state::LockScreenState& ctx) {
spdlog::debug("Output: {} {}", make, model); 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");
}
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<const char*>(glGetString(GL_VENDOR)));
spdlog::info("GL_RENDERER = {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
spdlog::info("GL_VERSION = {}",
reinterpret_cast<const char*>(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;
} }
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<state::Output*>(data); auto& out = *static_cast<state::Output*>(data);
if (flags & WL_OUTPUT_MODE_CURRENT) {
out.width = static_cast<uint32_t>(width); out.width = static_cast<uint32_t>(width);
out.height = static_cast<uint32_t>(height); out.height = static_cast<uint32_t>(height);
spdlog::info("Output mode: {}x{} @ {}Hz", width, height, refresh / 1000);
} spdlog::info("Output mode {}x{} @ {}Hz",
width,
height,
refresh / 1000);
} }
static void output_handle_done(void* data, wl_output* output) {} static void output_handle_done(void*, wl_output*) {}
static void output_handle_scale(void* data, wl_output* output, int32_t factor) {} static void output_handle_scale(void*, wl_output*, int32_t) {}
static const wl_output_listener output_listener = { static const wl_output_listener output_listener = {
.geometry = output_handle_geometry, .geometry = output_handle_geometry,
@@ -36,73 +161,61 @@ static const wl_output_listener output_listener = {
.scale = output_handle_scale, .scale = output_handle_scale,
}; };
// Registry callbacks - discover globals static void registry_handle_global(void* data,
static void registry_handle_global(
void* data,
wl_registry* registry, wl_registry* registry,
uint32_t name, uint32_t name,
const char* interface, const char* interface,
uint32_t version) uint32_t) {
{
auto& ctx = *static_cast<state::LockScreenState*>(data); auto& ctx = *static_cast<state::LockScreenState*>(data);
if (strcmp(interface, wl_compositor_interface.name) == 0) { if (strcmp(interface, wl_compositor_interface.name) == 0) {
ctx.compostier = static_cast<wl_compositor*>( ctx.compostier = static_cast<wl_compositor*>(
wl_registry_bind(registry, name, &wl_compositor_interface, 6) wl_registry_bind(registry,
); name,
spdlog::info("Bound to wl_compositor"); &wl_compositor_interface,
} 6));
else if (strcmp(interface, wl_output_interface.name) == 0) { } else if (strcmp(interface, wl_output_interface.name) == 0) {
auto output = std::make_shared<state::Output>(); auto out = std::make_shared<state::Output>();
output->output = static_cast<wl_output*>( out->output = static_cast<wl_output*>(
wl_registry_bind(registry, name, &wl_output_interface, 4) wl_registry_bind(registry,
); name,
output->registry_id = name; &wl_output_interface,
4));
out->registry_id = name;
ctx.outputs.push_back(output); wl_output_add_listener(out->output,
&output_listener,
out.get());
// Pass raw pointer to the Output object ctx.outputs.push_back(out);
wl_output_add_listener(output->output, &output_listener, output.get()); } else if (strcmp(interface,
ext_session_lock_manager_v1_interface.name) == 0) {
spdlog::info("Discovered output (registry_id={})", name); ctx.lock_manager =
} static_cast<ext_session_lock_manager_v1*>(
else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) { wl_registry_bind(registry,
ctx.lock_manager = static_cast<ext_session_lock_manager_v1*>( name,
wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, 1) &ext_session_lock_manager_v1_interface,
); 1));
spdlog::info("Bound to ext_session_lock_manager_v1");
} }
} }
static void registry_handle_global_remove(void* data, wl_registry* registry, uint32_t name) { static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}
auto& ctx = *static_cast<state::LockScreenState*>(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 const wl_registry_listener registry_listener = { static const wl_registry_listener registry_listener = {
.global = registry_handle_global, .global = registry_handle_global,
.global_remove = registry_handle_global_remove, .global_remove = registry_handle_global_remove,
}; };
static void lock_handle_locked(void* data, ext_session_lock_v1*) {
static void lock_handle_locked(void* data, ext_session_lock_v1* lock) {
auto& ctx = *static_cast<state::LockScreenState*>(data); auto& ctx = *static_cast<state::LockScreenState*>(data);
ctx.locked = true; 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<state::LockScreenState*>(data); auto& ctx = *static_cast<state::LockScreenState*>(data);
spdlog::error("Lock finished/denied by compositor - exiting");
ctx.running = false; ctx.running = false;
spdlog::error("Session lock denied/finished");
} }
static const ext_session_lock_v1_listener lock_listener = { static const ext_session_lock_v1_listener lock_listener = {
@@ -110,105 +223,126 @@ static const ext_session_lock_v1_listener lock_listener = {
.finished = lock_handle_finished, .finished = lock_handle_finished,
}; };
static void lock_surface_handle_configure( static void lock_surface_handle_configure(void* data,
void* data, ext_session_lock_surface_v1* surface,
ext_session_lock_surface_v1* lock_surface,
uint32_t serial, uint32_t serial,
uint32_t width, uint32_t width,
uint32_t height) uint32_t height) {
{
auto& out = *static_cast<state::Output*>(data); auto& out = *static_cast<state::Output*>(data);
// MUST acknowledge the configure ext_session_lock_surface_v1_ack_configure(surface, serial);
ext_session_lock_surface_v1_ack_configure(lock_surface, serial);
out.surface_width = width; out.surface_width = width;
out.surface_height = height; out.surface_height = height;
out.configured = true; out.configured = true;
spdlog::info("Lock surface configured: {}x{}", width, height); 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);
} }
static const ext_session_lock_surface_v1_listener lock_surface_listener = { static const ext_session_lock_surface_v1_listener lock_surface_listener = {
.configure = lock_surface_handle_configure, .configure = lock_surface_handle_configure,
}; };
// ============================================================================
// Main
// ============================================================================
int main (int argc, char *argv[]) { int main() {
std::shared_ptr<state::LockScreenState> application_state = std::make_shared<state::LockScreenState>(); auto ctx = std::make_shared<state::LockScreenState>();
application_state->display = wl_display_connect(nullptr);
if(!application_state->display) throw std::runtime_error("failed to initialize display"); ctx->display = wl_display_connect(nullptr);
spdlog::debug("Successfully initialized a display"); if (!ctx->display) {
throw std::runtime_error("Failed to connect to Wayland");
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, &registry_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");
}
if (!application_state->lock_manager) {
throw std::runtime_error("Compositor doesn't support ext_session_lock_manager_v1");
}
if (application_state->outputs.empty()) {
throw std::runtime_error("No outputs found");
} }
spdlog::info("Found {} output(s)", application_state->outputs.size()); wl_registry* registry = wl_display_get_registry(ctx->display);
wl_registry_add_listener(registry,
&registry_listener,
ctx.get());
wl_display_roundtrip(ctx->display);
spdlog::info("Found {} output(s)", application_state->outputs.size()); if (!ctx->compostier || !ctx->lock_manager || ctx->outputs.empty()) {
throw std::runtime_error("Required Wayland globals missing");
}
// Request lock session ctx->session_lock =
spdlog::info("Requesting session lock..."); ext_session_lock_manager_v1_lock(ctx->lock_manager);
application_state->session_lock = ext_session_lock_manager_v1_lock(application_state->lock_manager); ext_session_lock_v1_add_listener(ctx->session_lock,
ext_session_lock_v1_add_listener(application_state->session_lock, &lock_listener, application_state.get()); &lock_listener,
ctx.get());
// Create lock surface for each output for (auto& out : ctx->outputs) {
for (auto& output : application_state->outputs) { out->wl_out_surface =
// Create Wayland surface wl_compositor_create_surface(ctx->compostier);
output->wl_out_surface= wl_compositor_create_surface(application_state->compostier);
// Get lock surface for this output out->lock_surface =
output->lock_surface = ext_session_lock_v1_get_lock_surface( ext_session_lock_v1_get_lock_surface(
application_state->session_lock, ctx->session_lock,
output->wl_out_surface, out->wl_out_surface,
output->output out->output);
);
// Add listener for configure events
ext_session_lock_surface_v1_add_listener( ext_session_lock_surface_v1_add_listener(
output->lock_surface, out->lock_surface,
&lock_surface_listener, &lock_surface_listener,
output.get() out.get());
);
// Commit to trigger configure wl_surface_commit(out->wl_out_surface);
wl_surface_commit(output->wl_out_surface);
spdlog::debug("Created lock surface for output {}", output->registry_id);
} }
// Flush and wait for configure/locked events wl_display_roundtrip(ctx->display);
wl_display_flush(application_state->display);
wl_display_roundtrip(application_state->display);
if (!application_state->locked) { init_egl(*ctx);
throw std::runtime_error("Failed to acquire lock");
bool glad_loaded = false;
for (auto& out : ctx->outputs) {
create_egl_surface_for_output(*ctx,
*out,
!glad_loaded);
glad_loaded = true;
glViewport(0, 0,
out->surface_width,
out->surface_height);
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(ctx->egl_display, out->egl_surface);
} }
spdlog::info("✅ Lock acquired successfully!"); 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; return 0;
} }

View File

@@ -10,6 +10,10 @@ namespace state{
// IMPORTANT: Clear outputs BEFORE destroying display // IMPORTANT: Clear outputs BEFORE destroying display
// This ensures Output destructors run while display is still valid // This ensures Output destructors run while display is still valid
outputs.clear(); outputs.clear();
if (egl_display != EGL_NO_DISPLAY) {
eglTerminate(egl_display);
egl_display = EGL_NO_DISPLAY;
}
if (lock_manager) { if (lock_manager) {
ext_session_lock_manager_v1_destroy(lock_manager); ext_session_lock_manager_v1_destroy(lock_manager);
lock_manager = nullptr; lock_manager = nullptr;
@@ -35,6 +39,14 @@ namespace state{
} }
Output::~Output() { 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) { if (output) {
wl_output_destroy(output); wl_output_destroy(output);
output = nullptr; output = nullptr;

View File

@@ -9,6 +9,9 @@
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <wayland-egl-core.h> #include <wayland-egl-core.h>
#include "ext-session-lock-v1-client-protocol.h" #include "ext-session-lock-v1-client-protocol.h"
#include <EGL/egl.h>
#include <GLES2/gl2.h>
namespace state{ namespace state{
struct Output{ struct Output{
wl_output* output = nullptr; wl_output* output = nullptr;
@@ -21,6 +24,12 @@ namespace state{
bool configured = false; bool configured = false;
uint32_t surface_width = uint32_t {0}; uint32_t surface_width = uint32_t {0};
uint32_t surface_height = 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(); ~Output();
}; };
struct LockScreenState{ struct LockScreenState{
@@ -38,6 +47,9 @@ namespace state{
ext_session_lock_v1* session_lock = nullptr; ext_session_lock_v1* session_lock = nullptr;
bool locked=false; bool locked=false;
EGLDisplay egl_display = EGL_NO_DISPLAY;
EGLConfig egl_config = nullptr;
~LockScreenState(); ~LockScreenState();
}; };
} }