we wrote a single frame. yipeeeee
This commit is contained in:
420
src/main.cpp
420
src/main.cpp
@@ -1,214 +1,348 @@
|
||||
#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
|
||||
// ============================================================================
|
||||
|
||||
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);
|
||||
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);
|
||||
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<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);
|
||||
out.width = static_cast<uint32_t>(width);
|
||||
out.height = static_cast<uint32_t>(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<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 = 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 = {
|
||||
.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<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_width = width;
|
||||
out.surface_height = height;
|
||||
out.configured = true;
|
||||
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, ®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");
|
||||
}
|
||||
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,
|
||||
®istry_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,
|
||||
&lock_surface_listener,
|
||||
output.get()
|
||||
);
|
||||
ext_session_lock_surface_v1_add_listener(
|
||||
out->lock_surface,
|
||||
&lock_surface_listener,
|
||||
out.get());
|
||||
|
||||
// Commit to trigger configure
|
||||
wl_surface_commit(output->wl_out_surface);
|
||||
wl_surface_commit(out->wl_out_surface);
|
||||
}
|
||||
|
||||
spdlog::debug("Created lock surface for output {}", output->registry_id);
|
||||
}
|
||||
wl_display_roundtrip(ctx->display);
|
||||
|
||||
// Flush and wait for configure/locked events
|
||||
wl_display_flush(application_state->display);
|
||||
wl_display_roundtrip(application_state->display);
|
||||
init_egl(*ctx);
|
||||
|
||||
if (!application_state->locked) {
|
||||
throw std::runtime_error("Failed to acquire lock");
|
||||
}
|
||||
bool glad_loaded = false;
|
||||
|
||||
spdlog::info("✅ Lock acquired successfully!");
|
||||
for (auto& out : ctx->outputs) {
|
||||
create_egl_surface_for_output(*ctx,
|
||||
*out,
|
||||
!glad_loaded);
|
||||
glad_loaded = true;
|
||||
|
||||
return 0;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user