GPT is reall good at generating comments and fixing spelling error

This commit is contained in:
2025-12-23 13:28:51 -06:00
parent 9f1555d6e0
commit 9735df3ecb
11 changed files with 917 additions and 355 deletions

1
command.sh Normal file
View File

@@ -0,0 +1 @@
__EGL_VENDOR_LIBRARY_NAME=mesa LIBGL_ALWAYS_SOFTWARE=1 MESA_LOADER_DRIVER_OVERRIDE=llvmpipe ./build/exalock

100
math.md Normal file
View File

@@ -0,0 +1,100 @@
# Silk Particle Simulation: Mathematical Formulation
## 1. Stream Function
The velocity field is derived from a scalar stream function $\psi(x, y, t)$:
$$
\psi(x, y, t) = \sum_{i=1}^{4} A_i \Psi_i(x, y, t)
$$
where:
$$
\begin{align}
\Psi_1 &= \sin(\omega_1 x + \phi_1(t)) \sin(\omega_1 y) \\
\Psi_2 &= \sin(\omega_2 x) \sin(\omega_2 y + \phi_2(t)) \\
\Psi_3 &= \sin(\omega_3 r + \phi_3(t)), \quad r = \sqrt{(x - 0.5)^2 + (y - 0.5)^2} \\
\Psi_4 &= (x - 0.5)(y - 0.5) \sin(\omega_4 t)
\end{align}
$$
Suggested parameters: $A = (0.3, 0.25, 0.2, 0.15)$, $\omega = (2\pi, 3\pi, 4\pi, 0.5)$, $\phi_i(t) = c_i t$ with $c = (0.5, 0.3, 0.4)$
---
## 2. Velocity Field
Incompressible flow via curl of stream function:
$$
\mathbf{v} = \nabla \times \psi = \left(\frac{\partial \psi}{\partial y}, -\frac{\partial \psi}{\partial x}\right)
$$
This ensures $\nabla \cdot \mathbf{v} = 0$ (divergence-free).
---
## 3. Vorticity (for color)
Scalar vorticity equals the negative Laplacian of the stream function:
$$
\omega = \frac{\partial v_y}{\partial x} - \frac{\partial v_x}{\partial y} = -\nabla^2 \psi = -\left(\frac{\partial^2 \psi}{\partial x^2} + \frac{\partial^2 \psi}{\partial y^2}\right)
$$
---
## 4. Boundary Confinement
Soft quartic potential keeps particles on screen:
$$
V_{\text{boundary}}(x, y) = k \left[(x - 0.5)^4 + (y - 0.5)^4\right]
$$
Boundary force:
$$
\mathbf{F}_{\text{boundary}} = -\nabla V_{\text{boundary}} = -4k\left[(x - 0.5)^3, (y - 0.5)^3\right]
$$
---
## 5. Equations of Motion
Particle position $\mathbf{q} = (x, y)$ evolves as:
$$
\frac{d\mathbf{q}}{dt} = (1 - \alpha)\mathbf{v}(\mathbf{q}, t) + \beta \mathbf{F}_{\text{boundary}}(\mathbf{q})
$$
where $\alpha \approx 0.1$ (damping), $\beta \approx 0.05$ (boundary strength).
---
## 6. RK4 Integration
Fourth-order Runge-Kutta for state $\mathbf{s}$ with derivative $\mathbf{f}(\mathbf{s}, t)$:
$$
\begin{align}
\mathbf{k}_1 &= \mathbf{f}(\mathbf{s}, t) \\
\mathbf{k}_2 &= \mathbf{f}(\mathbf{s} + \tfrac{\Delta t}{2} \mathbf{k}_1, t + \tfrac{\Delta t}{2}) \\
\mathbf{k}_3 &= \mathbf{f}(\mathbf{s} + \tfrac{\Delta t}{2} \mathbf{k}_2, t + \tfrac{\Delta t}{2}) \\
\mathbf{k}_4 &= \mathbf{f}(\mathbf{s} + \Delta t \mathbf{k}_3, t + \Delta t) \\
\mathbf{s}_{\text{next}} &= \mathbf{s} + \tfrac{\Delta t}{6} (\mathbf{k}_1 + 2\mathbf{k}_2 + 2\mathbf{k}_3 + \mathbf{k}_4)
\end{align}
$$
---
## 7. Color Mapping
Map vorticity to HSV hue:
$$
H = \text{mod}\left(\frac{\omega - \omega_{\min}}{\omega_{\max} - \omega_{\min}} \cdot 360°, 360°\right), \quad S = 0.8, \quad V = 0.9
$$
Positive $\omega$ (CCW) → warm colors; negative $\omega$ (CW) → cool colors.

View File

@@ -0,0 +1,14 @@
#version 300 es
precision highp float;
in vec3 vColor;
in float vAlpha;
in vec2 vQuadCoord;
out vec4 fragColor;
void main() {
float dist = length(vQuadCoord);
float falloff = smoothstep(1.0, 0.5, dist);
fragColor = vec4(vColor, vAlpha * falloff);
}

View File

@@ -0,0 +1,82 @@
#version 300 es
precision highp float;
in vec2 quadVertex;
in vec2 particleID;
in float trailIndex;
uniform sampler2D stateTexture;
uniform float time;
uniform vec2 resolution;
out vec3 vColor;
out float vAlpha;
out vec2 vQuadCoord;
const float PI = 3.14159265359;
const float r_min = 0.002;
const float r_max = 0.008;
const float sigmoid_k = 20.0;
const float sigmoid_s0 = 0.15;
float sigmoid(float x) {
return 1.0 / (1.0 + exp(-sigmoid_k * (x - sigmoid_s0)));
}
float velocityToRadius(vec2 vel) {
float speed = length(vel);
return r_min + (r_max - r_min) * sigmoid(speed);
}
float streamFunction(vec2 q, float t) {
float x = q.x, y = q.y;
float phi1 = 0.5*t, phi2 = 0.3*t, phi3 = 0.4*t;
float r = length(q - vec2(0.5));
return 0.3*sin(2.0*PI*x + phi1)*sin(2.0*PI*y)
+ 0.25*sin(3.0*PI*x)*sin(3.0*PI*y + phi2)
+ 0.2*sin(4.0*PI*r + phi3)
+ 0.15*(x - 0.5)*(y - 0.5)*sin(0.5*t);
}
float computeVorticity(vec2 q, float t) {
const float h = 0.001;
float psi_c = streamFunction(q, t);
float psi_r = streamFunction(vec2(q.x + h, q.y), t);
float psi_l = streamFunction(vec2(q.x - h, q.y), t);
float psi_u = streamFunction(vec2(q.x, q.y + h), t);
float psi_d = streamFunction(vec2(q.x, q.y - h), t);
float d2psi_dx2 = (psi_r - 2.0*psi_c + psi_l) / (h*h);
float d2psi_dy2 = (psi_u - 2.0*psi_c + psi_d) / (h*h);
return -(d2psi_dx2 + d2psi_dy2);
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec2 texCoord = (particleID + 0.5) / 16.0;
vec4 state = texture(stateTexture, texCoord);
vec2 pos = state.rg;
vec2 vel = state.ba;
float radius = velocityToRadius(vel);
vec2 aspectRatio = vec2(resolution.y / resolution.x, 1.0);
vec2 screenPos = (pos * 2.0 - 1.0) * aspectRatio;
vec2 offset = quadVertex * radius * aspectRatio;
gl_Position = vec4(screenPos + offset, 0.0, 1.0);
float vorticity = computeVorticity(pos, time);
float hue = mod((vorticity + 3.0) / 6.0, 1.0);
vColor = hsv2rgb(vec3(hue, 0.8, 0.9));
vAlpha = (1.0 - trailIndex / 20.0) * 0.6;
vQuadCoord = quadVertex;
}

View File

@@ -0,0 +1,62 @@
#version 300 es
precision highp float;
in vec2 vTexCoord;
out vec4 fragColor;
uniform sampler2D stateTexture;
uniform float time;
uniform float dt;
const float PI = 3.14159265359;
const float alpha = 0.1;
const float beta = 0.05;
const float k = 2.0;
float streamFunction(vec2 q, float t) {
float x = q.x, y = q.y;
float phi1 = 0.5*t, phi2 = 0.3*t, phi3 = 0.4*t;
float r = length(q - vec2(0.5));
return 0.3*sin(2.0*PI*x + phi1)*sin(2.0*PI*y)
+ 0.25*sin(3.0*PI*x)*sin(3.0*PI*y + phi2)
+ 0.2*sin(4.0*PI*r + phi3)
+ 0.15*(x - 0.5)*(y - 0.5)*sin(0.5*t);
}
float dPsi_dx(vec2 q, float t) {
const float h = 0.001;
return (streamFunction(vec2(q.x + h, q.y), t) -
streamFunction(vec2(q.x - h, q.y), t)) / (2.0 * h);
}
float dPsi_dy(vec2 q, float t) {
const float h = 0.001;
return (streamFunction(vec2(q.x, q.y + h), t) -
streamFunction(vec2(q.x, q.y - h), t)) / (2.0 * h);
}
vec2 derivative(vec2 q, float t) {
vec2 vel = vec2(dPsi_dy(q, t), -dPsi_dx(q, t));
vec2 center_offset = q - vec2(0.5);
vec2 F_boundary = -4.0 * k * vec2(
pow(center_offset.x, 3.0),
pow(center_offset.y, 3.0)
);
return (1.0 - alpha) * vel + beta * F_boundary;
}
void main() {
vec4 state = texture(stateTexture, vTexCoord);
vec2 pos = state.rg;
// RK4 integration
vec2 k1 = derivative(pos, time);
vec2 k2 = derivative(pos + 0.5*dt*k1, time + 0.5*dt);
vec2 k3 = derivative(pos + 0.5*dt*k2, time + 0.5*dt);
vec2 k4 = derivative(pos + dt*k3, time + dt);
vec2 newPos = pos + (dt / 6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4);
vec2 vel = (k1 + 2.0*k2 + 2.0*k3 + k4) / 6.0;
fragColor = vec4(newPos, vel);
}

View File

@@ -0,0 +1,11 @@
#version 300 es
precision highp float;
in vec2 position; // Fullscreen quad vertices
out vec2 vTexCoord;
void main() {
vTexCoord = position * 0.5 + 0.5; // [-1,1] -> [0,1]
gl_Position = vec4(position, 0.0, 1.0);
}

View File

@@ -1,348 +1,544 @@
#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"
#include "shader_utils.hpp"
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <chrono>
#include <cstring>
#include <stdexcept>
#include <random>
#include <poll.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GLES3/gl3.h>
#include <wayland-egl-core.h>
#include "ext-session-lock-v1-client-protocol.h"
// ============================================================================
// EGL setup
// ============================================================================
#ifndef EGL_PLATFORM_WAYLAND_KHR
#define EGL_PLATFORM_WAYLAND_KHR 0x31D8
#endif
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");
// ============================================================
// Helper: Create shared memory buffer
// ============================================================
static int create_shm_file(size_t size) {
const char* path = getenv("XDG_RUNTIME_DIR");
if (!path) {
spdlog::error("XDG_RUNTIME_DIR not set");
return -1;
}
EGLint major = 0, minor = 0;
if (!eglInitialize(ctx.egl_display, &major, &minor)) {
throw std::runtime_error("Failed to initialize EGL");
std::string name = std::string(path) + "/exalock-XXXXXX";
int fd = mkstemp(&name[0]);
if (fd < 0) return -1;
unlink(name.c_str());
if (ftruncate(fd, static_cast<off_t>(size)) < 0) {
close(fd);
return -1;
}
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");
return fd;
}
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");
static wl_buffer* create_black_buffer(wl_shm* shm, int32_t width, int32_t height) {
int32_t stride = width * 4;
size_t size = static_cast<size_t>(stride) * static_cast<size_t>(height);
int fd = create_shm_file(size);
if (fd < 0) {
spdlog::error("Failed to create shm file");
return nullptr;
}
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");
void* data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return nullptr;
}
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);
memset(data, 0, size);
munmap(data, size);
wl_shm_pool* pool = wl_shm_create_pool(shm, fd, static_cast<int32_t>(size));
wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
return buffer;
}
// ============================================================================
// 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;
}
// ============================================================
// wl_output listener
// ============================================================
static void output_geometry(void*, wl_output*, int, int, int, int, int,
const char*, const char*, int) {}
static void output_mode(void* data, wl_output*, uint32_t flags,
int32_t width, int32_t height, int32_t) {
if (!(flags & WL_OUTPUT_MODE_CURRENT)) return;
auto& out = *static_cast<state::Output*>(data);
out.width = static_cast<uint32_t>(width);
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 void output_done(void*, wl_output*) {}
static void output_scale(void*, wl_output*, int32_t) {}
static void output_name(void*, wl_output*, const char*) {}
static void output_description(void*, wl_output*, const char*) {}
static const wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = output_handle_mode,
.done = output_handle_done,
.scale = output_handle_scale,
.geometry = output_geometry,
.mode = output_mode,
.done = output_done,
.scale = output_scale,
.name = output_name,
.description = output_description
};
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));
} 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;
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<ext_session_lock_manager_v1*>(
wl_registry_bind(registry,
name,
&ext_session_lock_manager_v1_interface,
1));
// ============================================================
// Lock surface listener
// ============================================================
static void lock_surface_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);
ext_session_lock_surface_v1_ack_configure(surface, serial);
out.surface_width = width;
out.surface_height = height;
out.configured = true;
if (out.shm && !out.buffer) {
out.buffer = create_black_buffer(out.shm,
static_cast<int32_t>(width),
static_cast<int32_t>(height));
if (out.buffer) {
wl_surface_attach(out.wl_surface, out.buffer, 0, 0);
wl_surface_damage(out.wl_surface, 0, 0,
static_cast<int32_t>(width),
static_cast<int32_t>(height));
wl_surface_commit(out.wl_surface);
spdlog::info("Lock surface configured with black buffer: {}x{}", width, height);
} else {
spdlog::error("Failed to create buffer for surface: {}x{}", width, height);
}
}
}
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 const ext_session_lock_surface_v1_listener lock_surface_listener = {
.configure = lock_surface_configure
};
static void lock_handle_locked(void* data, ext_session_lock_v1*) {
// ============================================================
// Session lock listener
// ============================================================
static void lock_locked(void* data, ext_session_lock_v1*) {
auto& ctx = *static_cast<state::LockScreenState*>(data);
ctx.locked = true;
spdlog::info("🔒 Session locked");
}
static void lock_handle_finished(void* data, ext_session_lock_v1*) {
static void lock_finished(void* data, ext_session_lock_v1*) {
auto& ctx = *static_cast<state::LockScreenState*>(data);
ctx.running = false;
spdlog::error("Session lock denied/finished");
spdlog::info("🔓 Session finished by compositor");
}
static const ext_session_lock_v1_listener lock_listener = {
.locked = lock_handle_locked,
.finished = lock_handle_finished,
.locked = lock_locked,
.finished = lock_finished
};
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);
// ============================================================
// Registry listener
// ============================================================
static void registry_add(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.compositor = static_cast<wl_compositor*>(
wl_registry_bind(registry, name, &wl_compositor_interface, 4)
);
spdlog::info("Bound wl_compositor");
}
else if (strcmp(interface, wl_shm_interface.name) == 0) {
ctx.shm = static_cast<wl_shm*>(
wl_registry_bind(registry, name, &wl_shm_interface, 1)
);
spdlog::info("Bound wl_shm");
}
else if (strcmp(interface, wl_output_interface.name) == 0) {
auto out = std::make_shared<state::Output>();
out->registry_id = name;
out->output = static_cast<wl_output*>(
wl_registry_bind(registry, name, &wl_output_interface, 4)
);
wl_output_add_listener(out->output, &output_listener, out.get());
ctx.outputs.push_back(out);
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 ext_session_lock_manager_v1");
}
}
static void registry_remove(void*, wl_registry*, uint32_t) {}
ext_session_lock_surface_v1_ack_configure(surface, serial);
static const wl_registry_listener registry_listener = {
.global = registry_add,
.global_remove = registry_remove
};
out.surface_width = width;
out.surface_height = height;
out.configured = true;
// ============================================================
// EGL initialization
// ============================================================
static void init_egl(state::LockScreenState& ctx) {
ctx.egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
static_cast<void*>(ctx.display),
nullptr);
spdlog::info("Lock surface configured {}x{}", width, height);
if (ctx.egl_display == EGL_NO_DISPLAY) {
spdlog::warn("eglGetPlatformDisplay failed, falling back to eglGetDisplay");
ctx.egl_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(ctx.display));
}
if (ctx.egl_display == EGL_NO_DISPLAY) {
spdlog::error("Failed to obtain EGL display");
throw std::runtime_error("No EGL display");
}
if (!eglInitialize(ctx.egl_display, nullptr, nullptr)) {
EGLint err = eglGetError();
spdlog::error("eglInitialize failed: 0x{:x}", err);
throw std::runtime_error("eglInitialize failed");
}
spdlog::info("EGL initialized successfully");
const EGLint cfg_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 n = 0;
if (!eglChooseConfig(ctx.egl_display, cfg_attribs, &ctx.egl_config, 1, &n) || n == 0) {
EGLint err = eglGetError();
spdlog::error("eglChooseConfig failed: 0x{:x}", err);
throw std::runtime_error("eglChooseConfig failed");
}
spdlog::info("EGL config selected");
}
static const ext_session_lock_surface_v1_listener lock_surface_listener = {
.configure = lock_surface_handle_configure,
};
// ============================================================
// Particle system initialization
// ============================================================
static void init_particle_system(state::LockScreenState& ctx) {
ctx.particles = std::make_shared<state::ParticleSystem>();
auto& ps = *ctx.particles;
ps.physics_program = shader::createProgram(
"shaders/physics_update.vert",
"shaders/physics_update.frag"
);
ps.render_program = shader::createProgram(
"shaders/particle_render.vert",
"shaders/particle_render.frag"
);
spdlog::info("Shaders compiled and linked");
ps.u_time = glGetUniformLocation(ps.physics_program, "time");
ps.u_dt = glGetUniformLocation(ps.physics_program, "dt");
ps.u_stateTexture = glGetUniformLocation(ps.physics_program, "stateTexture");
ps.u_resolution = glGetUniformLocation(ps.render_program, "resolution");
glGenTextures(2, ps.state_texture);
for (int i = 0; i < 2; ++i) {
glBindTexture(GL_TEXTURE_2D, ps.state_texture[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 16, 16, 0,
GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
std::vector<float> initial_state(16 * 16 * 4);
for (size_t i = 0; i < 256; ++i) {
initial_state[i * 4 + 0] = dist(gen);
initial_state[i * 4 + 1] = dist(gen);
initial_state[i * 4 + 2] = 0.0f;
initial_state[i * 4 + 3] = 0.0f;
}
glBindTexture(GL_TEXTURE_2D, ps.state_texture[0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_RGBA, GL_FLOAT, initial_state.data());
glGenFramebuffers(2, ps.fbo);
for (int i = 0; i < 2; ++i) {
glBindFramebuffer(GL_FRAMEBUFFER, ps.fbo[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, ps.state_texture[i], 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
spdlog::error("FBO {} incomplete: 0x{:x}", i, status);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
float quad_verts[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
glGenBuffers(1, &ps.quad_vbo);
glBindBuffer(GL_ARRAY_BUFFER, ps.quad_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_verts), quad_verts, GL_STATIC_DRAW);
float particle_quad[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
-1.0f, 1.0f
};
glGenBuffers(1, &ps.particle_vbo);
glBindBuffer(GL_ARRAY_BUFFER, ps.particle_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(particle_quad), particle_quad, GL_STATIC_DRAW);
std::vector<float> instance_data;
for (int p = 0; p < 256; ++p) {
instance_data.push_back(static_cast<float>(p % 16));
instance_data.push_back(static_cast<float>(p / 16));
instance_data.push_back(0.0f);
}
glGenBuffers(1, &ps.instance_vbo);
glBindBuffer(GL_ARRAY_BUFFER, ps.instance_vbo);
glBufferData(GL_ARRAY_BUFFER,
static_cast<GLsizeiptr>(instance_data.size() * sizeof(float)),
instance_data.data(), GL_STATIC_DRAW);
spdlog::info("Particle system initialized with 256 particles");
}
// ============================================================================
// ============================================================
// Main
// ============================================================================
// ============================================================
int main() {
auto file_logger = spdlog::basic_logger_mt("lockscreen_logger", "lockscreen.log");
spdlog::set_default_logger(file_logger);
spdlog::set_level(spdlog::level::info);
spdlog::info("Starting lockscreen program");
auto ctx = std::make_shared<state::LockScreenState>();
ctx->display = wl_display_connect(nullptr);
if (!ctx->display) {
throw std::runtime_error("Failed to connect to Wayland");
}
if (!ctx->display) throw std::runtime_error("Failed to connect to Wayland");
wl_registry* registry = wl_display_get_registry(ctx->display);
wl_registry_add_listener(registry,
&registry_listener,
ctx.get());
ctx->registry = wl_display_get_registry(ctx->display);
wl_registry_add_listener(ctx->registry, &registry_listener, ctx.get());
wl_display_roundtrip(ctx->display);
wl_display_roundtrip(ctx->display);
if (!ctx->compostier || !ctx->lock_manager || ctx->outputs.empty()) {
throw std::runtime_error("Required Wayland globals missing");
}
if (!ctx->compositor || !ctx->shm || !ctx->lock_manager || ctx->outputs.empty())
throw std::runtime_error("Missing required Wayland globals");
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());
spdlog::info("Found {} outputs, requesting session lock", ctx->outputs.size());
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->shm = ctx->shm;
out->wl_surface = wl_compositor_create_surface(ctx->compositor);
out->lock_surface = ext_session_lock_v1_get_lock_surface(
ctx->session_lock, out->wl_surface, out->output);
ext_session_lock_surface_v1_add_listener(out->lock_surface,
&lock_surface_listener, out.get());
}
wl_display_flush(ctx->display);
out->lock_surface =
ext_session_lock_v1_get_lock_surface(
ctx->session_lock,
out->wl_out_surface,
out->output);
spdlog::info("Waiting for lock confirmation...");
while (ctx->running) {
if (wl_display_dispatch(ctx->display) == -1) {
spdlog::error("Wayland connection lost during setup");
return 1;
}
bool ready = ctx->locked;
for (const auto& o : ctx->outputs) ready &= o->configured;
if (ready) break;
}
if (!ctx->locked) {
spdlog::error("Failed to acquire lock");
return 1;
}
spdlog::info("Lock acquired, initializing EGL");
ext_session_lock_surface_v1_add_listener(
out->lock_surface,
&lock_surface_listener,
out.get());
wl_surface_commit(out->wl_out_surface);
try {
init_egl(*ctx);
} catch (const std::exception& e) {
spdlog::error("EGL setup failed: {}", e.what());
return 1;
}
wl_display_roundtrip(ctx->display);
init_egl(*ctx);
bool glad_loaded = false;
EGLContext shared_context = EGL_NO_CONTEXT;
for (auto& out : ctx->outputs) {
create_egl_surface_for_output(*ctx,
*out,
!glad_loaded);
glad_loaded = true;
if (!out->configured || out->surface_width == 0 || out->surface_height == 0) continue;
glViewport(0, 0,
out->surface_width,
out->surface_height);
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
out->window = wl_egl_window_create(out->wl_surface,
static_cast<int>(out->surface_width),
static_cast<int>(out->surface_height));
if (!out->window) {
spdlog::warn("wl_egl_window_create failed for output {}", out->registry_id);
continue;
}
eglSwapBuffers(ctx->egl_display, out->egl_surface);
out->egl_surface = eglCreateWindowSurface(ctx->egl_display, ctx->egl_config,
out->window, nullptr);
if (out->egl_surface == EGL_NO_SURFACE) {
spdlog::warn("eglCreateWindowSurface failed for output {}", out->registry_id);
continue;
}
const EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
out->egl_context = eglCreateContext(ctx->egl_display, ctx->egl_config,
shared_context, ctx_attribs);
if (out->egl_context == EGL_NO_CONTEXT) {
spdlog::warn("eglCreateContext failed for output {}", out->registry_id);
continue;
}
if (shared_context == EGL_NO_CONTEXT) shared_context = out->egl_context;
}
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);
}
// Initialize particle system
auto& first_out = ctx->outputs[0];
eglMakeCurrent(ctx->egl_display, first_out->egl_surface,
first_out->egl_surface, first_out->egl_context);
try {
init_particle_system(*ctx);
} catch (const std::exception& e) {
spdlog::error("Particle system init failed: {}", e.what());
return 1;
}
auto start = std::chrono::steady_clock::now();
int fd = wl_display_get_fd(ctx->display);
while (ctx->running) {
struct pollfd pfd = { fd, POLLIN, 0 };
int ret = poll(&pfd, 1, 16);
if (ret > 0 && (pfd.revents & POLLIN)) {
if (wl_display_dispatch(ctx->display) == -1) break;
}
auto now = std::chrono::steady_clock::now();
float time = std::chrono::duration<float>(now - start).count();
float dt = 0.016f;
auto& ps = *ctx->particles;
for (auto& out : ctx->outputs) {
if (out->egl_surface == EGL_NO_SURFACE) continue;
eglMakeCurrent(ctx->egl_display, out->egl_surface,
out->egl_surface, out->egl_context);
// Physics update
glBindFramebuffer(GL_FRAMEBUFFER, ps.fbo[1 - ps.current_state]);
glViewport(0, 0, 16, 16);
glUseProgram(ps.physics_program);
glUniform1f(ps.u_time, time);
glUniform1f(ps.u_dt, dt);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, ps.state_texture[ps.current_state]);
glUniform1i(ps.u_stateTexture, 0);
glBindBuffer(GL_ARRAY_BUFFER, ps.quad_vbo);
GLint pos_attrib = glGetAttribLocation(ps.physics_program, "position");
glEnableVertexAttribArray(pos_attrib);
glVertexAttribPointer(pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Render particles
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, out->surface_width, out->surface_height);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(ps.render_program);
glUniform1f(glGetUniformLocation(ps.render_program, "time"), time);
glUniform2f(ps.u_resolution,
static_cast<float>(out->surface_width),
static_cast<float>(out->surface_height));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, ps.state_texture[1 - ps.current_state]);
glUniform1i(glGetUniformLocation(ps.render_program, "stateTexture"), 0);
glBindBuffer(GL_ARRAY_BUFFER, ps.particle_vbo);
GLint quad_attrib = glGetAttribLocation(ps.render_program, "quadVertex");
glEnableVertexAttribArray(quad_attrib);
glVertexAttribPointer(quad_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, ps.instance_vbo);
GLint pid_attrib = glGetAttribLocation(ps.render_program, "particleID");
GLint trail_attrib = glGetAttribLocation(ps.render_program, "trailIndex");
glEnableVertexAttribArray(pid_attrib);
glEnableVertexAttribArray(trail_attrib);
glVertexAttribPointer(pid_attrib, 2, GL_FLOAT, GL_FALSE,
3 * sizeof(float), 0);
glVertexAttribPointer(trail_attrib, 1, GL_FLOAT, GL_FALSE,
3 * sizeof(float),
reinterpret_cast<void*>(2 * sizeof(float)));
glVertexAttribDivisor(pid_attrib, 1);
glVertexAttribDivisor(trail_attrib, 1);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 256);
eglSwapBuffers(ctx->egl_display, out->egl_surface);
wl_surface_commit(out->wl_surface);
}
ps.current_state = 1 - ps.current_state;
wl_display_flush(ctx->display);
if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() >= 10)
break;
}
eglMakeCurrent(ctx->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (ctx->session_lock) {
ext_session_lock_v1_destroy(ctx->session_lock);
ext_session_lock_v1_unlock_and_destroy(ctx->session_lock);
}
wl_display_flush(ctx->display);
wl_display_roundtrip(ctx->display);
eglTerminate(ctx->egl_display);
wl_display_disconnect(ctx->display);
spdlog::info("Exited cleanly after one frame");
spdlog::info("Clean exit after 10 seconds");
return 0;
}

79
src/shader_utils.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "shader_utils.hpp"
#include <spdlog/spdlog.h>
#include <fstream>
#include <sstream>
namespace shader {
std::string loadFile(const char* path) {
std::ifstream file(path);
if (!file.is_open()) {
spdlog::error("Failed to open shader file: {}", path);
throw std::runtime_error(std::string("Cannot open shader: ") + path);
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char log[512];
glGetShaderInfoLog(shader, 512, nullptr, log);
spdlog::error("Shader compilation failed:\n{}", log);
glDeleteShader(shader);
throw std::runtime_error("Shader compilation failed");
}
return shader;
}
GLuint linkProgram(GLuint vertShader, GLuint fragShader) {
GLuint program = glCreateProgram();
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
GLint success = 0;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char log[512];
glGetProgramInfoLog(program, 512, nullptr, log);
spdlog::error("Program linking failed:\n{}", log);
glDeleteProgram(program);
throw std::runtime_error("Program linking failed");
}
return program;
}
GLuint createProgram(const char* vertPath, const char* fragPath) {
std::string vertSource = loadFile(vertPath);
std::string fragSource = loadFile(fragPath);
GLuint vertShader = compileShader(GL_VERTEX_SHADER, vertSource.c_str());
GLuint fragShader = compileShader(GL_FRAGMENT_SHADER, fragSource.c_str());
GLuint program = linkProgram(vertShader, fragShader);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
return program;
}
void checkGLError(const char* op) {
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
spdlog::error("GL error after {}: 0x{:x}", op, err);
}
}
} // namespace shader

21
src/shader_utils.hpp Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <GLES2/gl2.h>
#include <string>
#include <vector>
namespace shader {
// Load shader from file
std::string loadFile(const char* path);
// Compile shader
GLuint compileShader(GLenum type, const char* source);
// Link program
GLuint linkProgram(GLuint vertShader, GLuint fragShader);
// All-in-one: load, compile, link
GLuint createProgram(const char* vertPath, const char* fragPath);
// Check for GL errors
void checkGLError(const char* op);
}

View File

@@ -1,57 +1,36 @@
#include "state.hpp"
#include "ext-session-lock-v1-client-protocol.h"
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <wayland-egl-core.h>
namespace state{
LockScreenState::~LockScreenState() {
// IMPORTANT: Clear outputs BEFORE destroying display
// This ensures Output destructors run while display is still valid
namespace state {
LockScreenState::~LockScreenState() {
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;
}
if (compostier) {
wl_compositor_destroy(compostier);
compostier = nullptr;
}
if (compositer_registry) {
wl_registry_destroy(compositer_registry);
compositer_registry = nullptr;
}
if (display) {
wl_display_flush(display);
wl_display_disconnect(display);
display = nullptr;
}
if(session_lock) ext_session_lock_v1_destroy(session_lock);
locked=false;
if (egl_display != EGL_NO_DISPLAY) eglTerminate(egl_display);
if (lock_manager) ext_session_lock_manager_v1_destroy(lock_manager);
if (shm) wl_shm_destroy(shm);
if (compositor) wl_compositor_destroy(compositor);
if (registry) wl_registry_destroy(registry);
if (display) wl_display_disconnect(display);
running = false;
}
Output::~Output() {
if (egl_context != EGL_NO_CONTEXT) {
eglDestroyContext(eglGetCurrentDisplay(), egl_context);
egl_context = EGL_NO_CONTEXT;
if (egl_context != EGL_NO_CONTEXT) eglDestroyContext(eglGetCurrentDisplay(), egl_context);
if (egl_surface != EGL_NO_SURFACE) eglDestroySurface(eglGetCurrentDisplay(), egl_surface);
if (window) wl_egl_window_destroy(window);
if (buffer) wl_buffer_destroy(buffer);
if (lock_surface) ext_session_lock_surface_v1_destroy(lock_surface);
if (wl_surface) wl_surface_destroy(wl_surface);
if (output) wl_output_destroy(output);
}
if (egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(eglGetCurrentDisplay(), egl_surface);
egl_surface = EGL_NO_SURFACE;
}
if (output) {
wl_output_destroy(output);
output = nullptr;
}
if(lock_surface) ext_session_lock_surface_v1_destroy(lock_surface);
if(wl_out_surface) wl_surface_destroy(wl_out_surface);
ParticleSystem::~ParticleSystem() {
// Clean up OpenGL resources if any exist
if (physics_program) glDeleteProgram(physics_program);
if (render_program) glDeleteProgram(render_program);
if (fbo[0]) glDeleteBuffers(2, fbo);
if (state_texture[0]) glDeleteTextures(2, state_texture);
if (quad_vbo) glDeleteBuffers(1, &quad_vbo);
if (particle_vbo) glDeleteBuffers(1, &particle_vbo);
if (instance_vbo) glDeleteBuffers(1, &instance_vbo);
// You can leave some as empty if you're sure they're 0
}
}

View File

@@ -1,55 +1,72 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client-core.h>
#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>
#include <GLES3/gl3.h>
namespace state{
struct Output{
namespace state {
struct Output {
wl_output* output = nullptr;
uint32_t registry_id = 0;
uint32_t width = uint32_t {0};
uint32_t height = uint32_t {0};
wl_surface* wl_out_surface = nullptr;
uint32_t width = 0;
uint32_t height = 0;
wl_surface* wl_surface = nullptr;
ext_session_lock_surface_v1* lock_surface = nullptr;
bool configured = false;
uint32_t surface_width = uint32_t {0};
uint32_t surface_height = uint32_t {0};
uint32_t surface_width = 0;
uint32_t surface_height = 0;
wl_shm* shm = nullptr;
wl_buffer* buffer = nullptr;
wl_egl_window* window = nullptr;
EGLSurface egl_surface = EGL_NO_SURFACE;
EGLContext egl_context = EGL_NO_CONTEXT;
~Output();
};
struct LockScreenState{
struct ParticleSystem {
GLuint physics_program = 0;
GLuint render_program = 0;
GLuint fbo[2] = {0, 0};
GLuint state_texture[2] = {0, 0};
int current_state = 0;
GLuint quad_vbo = 0;
GLuint particle_vbo = 0;
GLuint instance_vbo = 0;
GLint u_time = -1;
GLint u_dt = -1;
GLint u_resolution = -1;
GLint u_stateTexture = -1;
~ParticleSystem();
};
struct LockScreenState {
wl_display* display = nullptr;
wl_registry* compositer_registry = nullptr;
wl_compositor* compostier = nullptr;
wl_registry* registry = nullptr;
wl_compositor* compositor = nullptr;
wl_shm* shm = nullptr;
ext_session_lock_manager_v1* lock_manager = nullptr;
std::string username = std::string {};
std::string password = std::string {};
std::vector<std::shared_ptr<Output>> outputs = std::vector<std::shared_ptr<Output>>{};
std::vector<std::shared_ptr<Output>> outputs;
bool running = true;
ext_session_lock_v1* session_lock = nullptr;
bool locked=false;
bool locked = false;
EGLDisplay egl_display = EGL_NO_DISPLAY;
EGLConfig egl_config = nullptr;
std::shared_ptr<ParticleSystem> particles;
~LockScreenState();
};
}