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 <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <vector>
#include <glad/gles2.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <wayland-egl-core.h>
#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<state::Output*>(data);
spdlog::debug("Output: {} {}", make, model);
// ============================================================================
// EGL setup
// ============================================================================
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_mode(void* data, wl_output* output,
uint32_t flags, int32_t width, int32_t height, int32_t refresh)
{
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;
}
auto& out = *static_cast<state::Output*>(data);
if (flags & WL_OUTPUT_MODE_CURRENT) {
out.width = static_cast<uint32_t>(width);
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_scale(void* data, wl_output* output, int32_t factor) {}
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,
@@ -36,73 +161,61 @@ static const wl_output_listener output_listener = {
.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,
uint32_t name,
const char* interface,
uint32_t version)
{
uint32_t) {
auto& ctx = *static_cast<state::LockScreenState*>(data);
if (strcmp(interface, wl_compositor_interface.name) == 0) {
ctx.compostier = static_cast<wl_compositor*>(
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<state::Output>();
output->output = static_cast<wl_output*>(
wl_registry_bind(registry, name, &wl_output_interface, 4)
);
output->registry_id = name;
wl_registry_bind(registry,
name,
&wl_compositor_interface,
6));
} else if (strcmp(interface, wl_output_interface.name) == 0) {
auto out = std::make_shared<state::Output>();
out->output = static_cast<wl_output*>(
wl_registry_bind(registry,
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
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<ext_session_lock_manager_v1*>(
wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, 1)
);
spdlog::info("Bound to ext_session_lock_manager_v1");
ctx.outputs.push_back(out);
} else if (strcmp(interface,
ext_session_lock_manager_v1_interface.name) == 0) {
ctx.lock_manager =
static_cast<ext_session_lock_manager_v1*>(
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<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 void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}
static const wl_registry_listener registry_listener = {
.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<state::LockScreenState*>(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<state::LockScreenState*>(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 = {
@@ -110,105 +223,126 @@ static const ext_session_lock_v1_listener lock_listener = {
.finished = lock_handle_finished,
};
static void lock_surface_handle_configure(
void* data,
ext_session_lock_surface_v1* lock_surface,
static void lock_surface_handle_configure(void* data,
ext_session_lock_surface_v1* surface,
uint32_t serial,
uint32_t width,
uint32_t height)
{
uint32_t height) {
auto& out = *static_cast<state::Output*>(data);
// MUST acknowledge the configure
ext_session_lock_surface_v1_ack_configure(lock_surface, serial);
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);
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<state::LockScreenState> application_state = std::make_shared<state::LockScreenState>();
application_state->display = wl_display_connect(nullptr);
int main() {
auto ctx = std::make_shared<state::LockScreenState>();
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, &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");
ctx->display = wl_display_connect(nullptr);
if (!ctx->display) {
throw std::runtime_error("Failed to connect to Wayland");
}
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
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());
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());
// 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);
for (auto& out : ctx->outputs) {
out->wl_out_surface =
wl_compositor_create_surface(ctx->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
);
out->lock_surface =
ext_session_lock_v1_get_lock_surface(
ctx->session_lock,
out->wl_out_surface,
out->output);
// Add listener for configure events
ext_session_lock_surface_v1_add_listener(
output->lock_surface,
out->lock_surface,
&lock_surface_listener,
output.get()
);
out.get());
// Commit to trigger configure
wl_surface_commit(output->wl_out_surface);
spdlog::debug("Created lock surface for output {}", output->registry_id);
wl_surface_commit(out->wl_out_surface);
}
// Flush and wait for configure/locked events
wl_display_flush(application_state->display);
wl_display_roundtrip(application_state->display);
wl_display_roundtrip(ctx->display);
if (!application_state->locked) {
throw std::runtime_error("Failed to acquire lock");
init_egl(*ctx);
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;
}

View File

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

View File

@@ -9,6 +9,9 @@
#include <wayland-client-protocol.h>
#include <wayland-egl-core.h>
#include "ext-session-lock-v1-client-protocol.h"
#include <EGL/egl.h>
#include <GLES2/gl2.h>
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();
};
}