2024-06-02 19:29:26 +02:00
|
|
|
#include <bits/time.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdio.h>
|
2026-11-08 14:46:04 +01:00
|
|
|
#include <stdlib.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include <time.h>
|
2026-11-08 14:46:04 +01:00
|
|
|
#include <math.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <syscall.h>
|
|
|
|
|
#include <unistd.h>
|
2026-11-08 14:46:04 +01:00
|
|
|
#include <linux/input-event-codes.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
|
|
|
|
|
#include <xdg-shell.h>
|
2026-11-08 14:46:04 +01:00
|
|
|
#include <viewporter.h>
|
|
|
|
|
#include <fractional-scale-v1.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
#include <wayland-client.h>
|
2026-06-13 10:22:08 +02:00
|
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
|
|
|
|
|
#include <tkeyboard.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
#include <tpointer.h>
|
2026-11-08 14:46:04 +01:00
|
|
|
#include <config.h>
|
|
|
|
|
#include <grid.h>
|
|
|
|
|
#include <pattern.h>
|
2024-06-02 19:29:26 +02:00
|
|
|
|
|
|
|
|
struct wl_compositor *compositor;
|
|
|
|
|
struct wl_shm *shm;
|
|
|
|
|
struct xdg_wm_base *xdg_shell;
|
|
|
|
|
struct wl_seat *wl_seat;
|
2026-11-08 14:46:04 +01:00
|
|
|
struct wl_output *wl_output = NULL;
|
|
|
|
|
int output_scale_factor = 1;
|
2026-11-08 16:07:45 +01:00
|
|
|
int output_width = 0;
|
|
|
|
|
int output_height = 0;
|
2024-06-02 19:29:26 +02:00
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
struct keyboard_data keyboard_data;
|
|
|
|
|
struct pointer_data pointer_data;
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
int running = 1;
|
|
|
|
|
int released = 0;
|
2026-11-08 14:46:04 +01:00
|
|
|
|
|
|
|
|
config_t g_config;
|
|
|
|
|
int active_tool = -1; /* -1 none, 0..3 patterns */
|
|
|
|
|
int active_rotation = 0;
|
|
|
|
|
|
|
|
|
|
int logical_width = 400;
|
|
|
|
|
int logical_height = 400;
|
|
|
|
|
double current_scale = 1.0;
|
|
|
|
|
int need_resize = 1;
|
|
|
|
|
|
|
|
|
|
grid_t grid;
|
2026-11-08 15:47:46 +01:00
|
|
|
grid_t grid_back;
|
|
|
|
|
int sim_paused = 1;
|
|
|
|
|
double sim_interval = 0.05; /* 20 tps */
|
|
|
|
|
double sim_accumulator = 0.0;
|
|
|
|
|
struct timespec sim_last_time;
|
|
|
|
|
int sim_time_initialized = 0;
|
|
|
|
|
int mouse_left_down = 0;
|
|
|
|
|
int mouse_right_down = 0;
|
2026-11-08 14:46:04 +01:00
|
|
|
double cursor_x = 0, cursor_y = 0;
|
|
|
|
|
int click_pending = 0;
|
|
|
|
|
double click_x = 0, click_y = 0;
|
|
|
|
|
int show_grid_lines = 1;
|
2024-06-02 19:29:26 +02:00
|
|
|
struct timespec mytime;
|
|
|
|
|
|
|
|
|
|
struct canvas
|
|
|
|
|
{
|
2026-11-08 14:46:04 +01:00
|
|
|
int width; /* physical */
|
|
|
|
|
int height; /* physical */
|
2024-06-02 19:29:26 +02:00
|
|
|
int stride;
|
|
|
|
|
int size;
|
|
|
|
|
struct wl_buffer *buffer;
|
|
|
|
|
unsigned char *data;
|
2026-11-08 14:46:04 +01:00
|
|
|
struct wl_shm_pool *pool;
|
|
|
|
|
int fd;
|
2024-06-02 19:29:26 +02:00
|
|
|
struct wl_surface *surface;
|
|
|
|
|
struct wl_callback *frame;
|
|
|
|
|
} canvas;
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
struct wp_viewporter *viewporter = NULL;
|
|
|
|
|
struct wp_viewport *canvas_viewport = NULL;
|
|
|
|
|
struct wp_fractional_scale_manager_v1 *fractional_manager = NULL;
|
|
|
|
|
struct wp_fractional_scale_v1 *fractional_scale = NULL;
|
|
|
|
|
|
|
|
|
|
// output listener
|
|
|
|
|
static void output_geometry_handler(void *data, struct wl_output *output, int32_t x, int32_t y, int32_t phys_w, int32_t phys_h, int32_t subpixel, const char *make, const char *model, int32_t transform) { (void)data;(void)output;(void)x;(void)y;(void)phys_w;(void)phys_h;(void)subpixel;(void)make;(void)model;(void)transform; }
|
2026-11-08 16:07:45 +01:00
|
|
|
static void output_mode_handler(void *data, struct wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
|
|
|
|
(void)data;(void)output;(void)flags;(void)refresh;
|
|
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
|
output_width = width;
|
|
|
|
|
output_height = height;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
static void output_done_handler(void *data, struct wl_output *output) { (void)data;(void)output; }
|
|
|
|
|
static void output_scale_handler(void *data, struct wl_output *output, int32_t factor) {
|
|
|
|
|
(void)data;(void)output;
|
|
|
|
|
output_scale_factor = factor;
|
|
|
|
|
if (!fractional_manager) {
|
|
|
|
|
if (factor > 0) current_scale = factor;
|
|
|
|
|
need_resize = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const struct wl_output_listener output_listener = {
|
|
|
|
|
.geometry = output_geometry_handler,
|
|
|
|
|
.mode = output_mode_handler,
|
|
|
|
|
.done = output_done_handler,
|
|
|
|
|
.scale = output_scale_handler,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// fractional scale listener
|
|
|
|
|
static void fractional_preferred_scale_handler(void *data, struct wp_fractional_scale_v1 *scale_obj, uint32_t scale) {
|
|
|
|
|
(void)data;(void)scale_obj;
|
|
|
|
|
double s = (double)scale / 120.0;
|
|
|
|
|
if (s > 0) current_scale = s;
|
|
|
|
|
need_resize = 1;
|
|
|
|
|
}
|
|
|
|
|
const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
|
|
|
|
|
.preferred_scale = fractional_preferred_scale_handler,
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
// registry
|
|
|
|
|
void registry_global_handler(
|
|
|
|
|
void *data, struct wl_registry *registry,
|
|
|
|
|
uint32_t name, const char *interface,
|
|
|
|
|
uint32_t version)
|
|
|
|
|
{
|
|
|
|
|
// printf(" -> %s\n", interface);
|
|
|
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, wl_compositor_interface.version);
|
|
|
|
|
printf("Got wl_compositor %d\n", version);
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(interface, wl_shm_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
shm = wl_registry_bind(registry, name, &wl_shm_interface, wl_shm_interface.version);
|
|
|
|
|
printf("Got wl_shm %d\n", version);
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(interface, xdg_wm_base_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
xdg_shell = wl_registry_bind(registry, name, &xdg_wm_base_interface, 4);
|
|
|
|
|
printf("Got xdg_wm_base %d\n", version);
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(interface, wl_seat_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5);
|
|
|
|
|
printf("Got wl_seat (%d)\n", version);
|
|
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
else if (strcmp(interface, wl_output_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
if (!wl_output)
|
|
|
|
|
{
|
|
|
|
|
wl_output = wl_registry_bind(registry, name, &wl_output_interface, 2);
|
|
|
|
|
wl_output_add_listener(wl_output, &output_listener, NULL);
|
|
|
|
|
printf("Got wl_output (%d)\n", version);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(interface, wp_viewporter_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
|
|
|
|
|
printf("Got wp_viewporter (%d)\n", version);
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0)
|
|
|
|
|
{
|
|
|
|
|
fractional_manager = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1);
|
|
|
|
|
printf("Got fractional_scale_manager (%d)\n", version);
|
|
|
|
|
}
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void registry_global_remove_handler(
|
|
|
|
|
void *data, struct wl_registry *registry,
|
|
|
|
|
uint32_t name)
|
|
|
|
|
{
|
2024-06-02 19:29:26 +02:00
|
|
|
// nothing
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_registry_listener registry_listener = {
|
|
|
|
|
.global = registry_global_handler,
|
|
|
|
|
.global_remove = registry_global_remove_handler};
|
|
|
|
|
|
|
|
|
|
// xdg_toplevel
|
|
|
|
|
void xdg_toplevel_configure_handler(
|
|
|
|
|
void *data,
|
|
|
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
int32_t width, int32_t height,
|
|
|
|
|
struct wl_array *states)
|
|
|
|
|
{
|
|
|
|
|
printf("xdg_toplevel_configure: %dx%d\n", width, height);
|
2026-11-08 14:46:04 +01:00
|
|
|
if (width > 0) logical_width = width;
|
|
|
|
|
if (height > 0) logical_height = height;
|
|
|
|
|
need_resize = 1;
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void xdg_toplevel_close_handler(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
|
|
|
{
|
|
|
|
|
printf("xdg_toplevel_close\n");
|
|
|
|
|
running = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void xdg_toplevel_configure_bounds_handler(
|
|
|
|
|
void *data,
|
|
|
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
int32_t width,
|
|
|
|
|
int32_t height)
|
|
|
|
|
{
|
|
|
|
|
// nothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
|
|
|
.configure = xdg_toplevel_configure_handler,
|
|
|
|
|
.close = xdg_toplevel_close_handler,
|
|
|
|
|
.configure_bounds = xdg_toplevel_configure_bounds_handler};
|
|
|
|
|
|
|
|
|
|
// xdg_surface
|
|
|
|
|
void xdg_surface_configure_handler(void *data, struct xdg_surface *xdg_surface,
|
|
|
|
|
uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
printf("xdg_surface_configure\n");
|
|
|
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct xdg_surface_listener xdg_surface_listener = {
|
|
|
|
|
.configure = xdg_surface_configure_handler};
|
|
|
|
|
|
|
|
|
|
// xdg_shell
|
|
|
|
|
void xdg_wm_base_ping_handler(void *data, struct xdg_wm_base *xdg_shell,
|
|
|
|
|
uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
xdg_wm_base_pong(xdg_shell, serial);
|
|
|
|
|
// printf("wm_base ping-pong\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct xdg_wm_base_listener xdg_shell_listener = {
|
|
|
|
|
.ping = xdg_wm_base_ping_handler};
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
void wl_buffer_release_handler(void *data, struct wl_buffer *wl_buffer)
|
|
|
|
|
{
|
2024-06-02 19:29:26 +02:00
|
|
|
// printf("wl_buffer_release\n");
|
|
|
|
|
released = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_buffer_listener wl_buffer_listener = {
|
|
|
|
|
.release = wl_buffer_release_handler};
|
|
|
|
|
|
|
|
|
|
void redraw(); // forward declaration
|
|
|
|
|
|
2026-11-08 15:47:46 +01:00
|
|
|
static void simulation_tick(void);
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t mods, void *user_data)
|
2026-06-13 10:22:08 +02:00
|
|
|
{
|
|
|
|
|
(void)key;
|
|
|
|
|
(void)user_data;
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return;
|
|
|
|
|
|
|
|
|
|
action_t a = config_lookup(&g_config, mods, sym);
|
|
|
|
|
printf("action=%d\n", a);
|
|
|
|
|
|
|
|
|
|
switch (a) {
|
|
|
|
|
case ACTION_QUIT:
|
2026-06-13 10:22:08 +02:00
|
|
|
running = 0;
|
2026-11-08 14:46:04 +01:00
|
|
|
break;
|
|
|
|
|
case ACTION_CANCEL:
|
|
|
|
|
active_tool = -1;
|
|
|
|
|
printf("tool cancelled\n");
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_ROTATE:
|
|
|
|
|
if (active_tool >= 0) {
|
|
|
|
|
active_rotation = (active_rotation + 1) % 4;
|
|
|
|
|
printf("rotation=%d\n", active_rotation);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_SELECT_GLIDER:
|
|
|
|
|
active_tool = 0; active_rotation = 0; printf("selected glider\n"); break;
|
|
|
|
|
case ACTION_SELECT_BLINKER:
|
|
|
|
|
active_tool = 1; active_rotation = 0; printf("selected blinker\n"); break;
|
|
|
|
|
case ACTION_SELECT_BLOCK:
|
|
|
|
|
active_tool = 2; active_rotation = 0; printf("selected block\n"); break;
|
|
|
|
|
case ACTION_SELECT_TOAD:
|
|
|
|
|
active_tool = 3; active_rotation = 0; printf("selected toad\n"); break;
|
|
|
|
|
case ACTION_CLEAR:
|
|
|
|
|
printf("clear requested\n");
|
|
|
|
|
break;
|
|
|
|
|
case ACTION_PAUSE:
|
|
|
|
|
sim_paused = !sim_paused;
|
2026-11-08 15:47:46 +01:00
|
|
|
sim_accumulator = 0;
|
|
|
|
|
printf("paused=%d\n", sim_paused);
|
2026-11-08 14:46:04 +01:00
|
|
|
break;
|
|
|
|
|
case ACTION_STEP:
|
2026-11-08 15:47:46 +01:00
|
|
|
if (sim_paused) {
|
|
|
|
|
simulation_tick();
|
|
|
|
|
printf("stepped\n");
|
|
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
break;
|
|
|
|
|
case ACTION_SPEED_UP:
|
2026-11-08 15:47:46 +01:00
|
|
|
sim_interval /= 2.0;
|
|
|
|
|
if (sim_interval < 0.025) sim_interval = 0.025;
|
|
|
|
|
printf("interval=%g\n", sim_interval);
|
|
|
|
|
break;
|
2026-11-08 14:46:04 +01:00
|
|
|
case ACTION_SPEED_DOWN:
|
2026-11-08 15:47:46 +01:00
|
|
|
sim_interval *= 2.0;
|
|
|
|
|
if (sim_interval > 1.0) sim_interval = 1.0;
|
|
|
|
|
printf("interval=%g\n", sim_interval);
|
|
|
|
|
break;
|
2026-11-08 14:46:04 +01:00
|
|
|
case ACTION_TOGGLE_GRID:
|
|
|
|
|
show_grid_lines = !show_grid_lines;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2026-06-13 10:22:08 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
static void handle_button(uint32_t button, uint32_t state, double x, double y, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
(void)user_data;
|
2026-11-08 15:47:46 +01:00
|
|
|
if (button == BTN_LEFT) {
|
|
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
|
|
|
mouse_left_down = 1;
|
|
|
|
|
click_x = x;
|
|
|
|
|
click_y = y;
|
|
|
|
|
click_pending = 1;
|
|
|
|
|
} else {
|
|
|
|
|
mouse_left_down = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (button == BTN_RIGHT) {
|
|
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
|
|
|
mouse_right_down = 1;
|
|
|
|
|
} else {
|
|
|
|
|
mouse_right_down = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
printf("button %u state %u at %.1f %.1f\n", button, state, x, y);
|
2026-11-08 14:46:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time)
|
|
|
|
|
{
|
|
|
|
|
if (canvas.frame)
|
|
|
|
|
{
|
2024-06-02 19:29:26 +02:00
|
|
|
wl_callback_destroy(canvas.frame);
|
|
|
|
|
canvas.frame = NULL;
|
|
|
|
|
}
|
2024-06-02 19:29:26 +02:00
|
|
|
if (released)
|
|
|
|
|
{
|
2024-06-02 19:29:26 +02:00
|
|
|
redraw();
|
|
|
|
|
released = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct wl_callback_listener wl_surface_frame_listener = {
|
|
|
|
|
.done = wl_callack_done};
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
static void canvas_resize(int log_w, int log_h, double scale) {
|
|
|
|
|
if (log_w < 1) log_w = 1;
|
|
|
|
|
if (log_h < 1) log_h = 1;
|
|
|
|
|
if (scale <= 0) scale = 1.0;
|
|
|
|
|
int phys_w = (int)ceil((double)log_w * scale);
|
|
|
|
|
int phys_h = (int)ceil((double)log_h * scale);
|
|
|
|
|
if (phys_w < 1) phys_w = 1;
|
|
|
|
|
if (phys_h < 1) phys_h = 1;
|
|
|
|
|
|
|
|
|
|
if (canvas.buffer) { wl_buffer_destroy(canvas.buffer); canvas.buffer = NULL; }
|
|
|
|
|
if (canvas.pool) { wl_shm_pool_destroy(canvas.pool); canvas.pool = NULL; }
|
|
|
|
|
if (canvas.data && canvas.size > 0) { munmap(canvas.data, canvas.size); canvas.data = NULL; }
|
|
|
|
|
if (canvas.fd >= 0) { close(canvas.fd); canvas.fd = -1; }
|
|
|
|
|
|
|
|
|
|
canvas.width = phys_w;
|
|
|
|
|
canvas.height = phys_h;
|
|
|
|
|
canvas.stride = phys_w * 4;
|
|
|
|
|
canvas.size = canvas.stride * phys_h;
|
|
|
|
|
|
|
|
|
|
canvas.fd = syscall(SYS_memfd_create, "buffer", 0);
|
|
|
|
|
ftruncate(canvas.fd, canvas.size);
|
|
|
|
|
canvas.data = mmap(NULL, canvas.size, PROT_READ | PROT_WRITE, MAP_SHARED, canvas.fd, 0);
|
|
|
|
|
canvas.pool = wl_shm_create_pool(shm, canvas.fd, canvas.size);
|
|
|
|
|
close(canvas.fd);
|
|
|
|
|
canvas.fd = -1;
|
|
|
|
|
|
|
|
|
|
canvas.buffer = wl_shm_pool_create_buffer(canvas.pool, 0, phys_w, phys_h, canvas.stride, WL_SHM_FORMAT_ARGB8888);
|
|
|
|
|
wl_buffer_add_listener(canvas.buffer, &wl_buffer_listener, NULL);
|
|
|
|
|
|
|
|
|
|
if (canvas_viewport) {
|
|
|
|
|
wp_viewport_set_destination(canvas_viewport, log_w, log_h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grid_resize(&grid, log_w / 8, log_h / 8);
|
2026-11-08 15:47:46 +01:00
|
|
|
grid_resize(&grid_back, grid.cols, grid.rows);
|
2026-11-08 14:46:04 +01:00
|
|
|
need_resize = 0;
|
|
|
|
|
printf("canvas resized logical=%dx%d phys=%dx%d scale=%g grid=%dx%d\n", log_w, log_h, phys_w, phys_h, scale, grid.cols, grid.rows);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &mytime);
|
|
|
|
|
|
|
|
|
|
struct wl_display *display = wl_display_connect(NULL);
|
|
|
|
|
struct wl_registry *registry = wl_display_get_registry(display);
|
|
|
|
|
wl_registry_add_listener(registry, ®istry_listener, NULL);
|
|
|
|
|
|
|
|
|
|
// wait for the "initial" set of globals to appear
|
|
|
|
|
wl_display_roundtrip(display);
|
|
|
|
|
|
|
|
|
|
xdg_wm_base_add_listener(xdg_shell, &xdg_shell_listener, NULL);
|
|
|
|
|
|
|
|
|
|
canvas.surface = wl_compositor_create_surface(compositor);
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
if (viewporter) {
|
|
|
|
|
canvas_viewport = wp_viewporter_get_viewport(viewporter, canvas.surface);
|
|
|
|
|
}
|
|
|
|
|
if (fractional_manager) {
|
|
|
|
|
fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(fractional_manager, canvas.surface);
|
|
|
|
|
wp_fractional_scale_v1_add_listener(fractional_scale, &fractional_scale_listener, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
struct xdg_surface *xdg_surface = xdg_wm_base_get_xdg_surface(xdg_shell, canvas.surface);
|
|
|
|
|
struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
|
|
|
|
|
|
|
|
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
|
|
|
|
|
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
|
|
|
|
|
|
|
|
|
|
xdg_toplevel_set_app_id(xdg_toplevel, "eu.ar76.wlgol");
|
|
|
|
|
xdg_toplevel_set_title(xdg_toplevel, "Game Of Life.");
|
|
|
|
|
|
|
|
|
|
// signal that the surface is ready to be configured
|
|
|
|
|
wl_surface_commit(canvas.surface);
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
keyboard_data.keyboard = wl_seat_get_keyboard(wl_seat);
|
2026-06-13 10:22:08 +02:00
|
|
|
keyboard_set_handler(&keyboard_data, handle_key, NULL);
|
2024-06-02 19:29:26 +02:00
|
|
|
wl_keyboard_add_listener(keyboard_data.keyboard, &keyboard_listener, &keyboard_data);
|
|
|
|
|
|
|
|
|
|
pointer_init_cursor(&pointer_data, compositor, shm);
|
2026-11-08 14:46:04 +01:00
|
|
|
pointer_set_button_handler(&pointer_data, handle_button, NULL);
|
2024-06-02 19:29:26 +02:00
|
|
|
pointer_data.pointer = wl_seat_get_pointer(wl_seat);
|
|
|
|
|
wl_pointer_add_listener(pointer_data.pointer, &pointer_listener, &pointer_data);
|
2024-06-02 19:29:26 +02:00
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
canvas.fd = -1;
|
2026-11-08 16:07:45 +01:00
|
|
|
logical_width = 1024;
|
|
|
|
|
logical_height = 576;
|
2026-11-08 14:46:04 +01:00
|
|
|
grid_init(&grid, logical_width / 8, logical_height / 8);
|
|
|
|
|
grid.show_lines = 1;
|
2024-06-02 19:29:26 +02:00
|
|
|
|
|
|
|
|
// wait for the surface to be configured
|
|
|
|
|
wl_display_roundtrip(display);
|
|
|
|
|
|
2026-11-08 16:07:45 +01:00
|
|
|
if (output_width > 0 && output_height > 0 && current_scale > 0) {
|
|
|
|
|
logical_width = (int)(output_width * 0.8 / current_scale);
|
|
|
|
|
logical_height = (int)(output_height * 0.8 / current_scale);
|
|
|
|
|
}
|
|
|
|
|
if (logical_width < 8) logical_width = 8;
|
|
|
|
|
if (logical_height < 8) logical_height = 8;
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
/* initial scale may arrive via fractional/output after roundtrip */
|
|
|
|
|
if (fractional_manager && !fractional_scale) {
|
|
|
|
|
/* already created above */
|
|
|
|
|
}
|
|
|
|
|
canvas_resize(logical_width, logical_height, current_scale);
|
|
|
|
|
|
|
|
|
|
const char *home = getenv("HOME");
|
|
|
|
|
char config_path[1024];
|
|
|
|
|
if (home) snprintf(config_path, sizeof(config_path), "%s/.config/wlgol/keys.conf", home);
|
|
|
|
|
else snprintf(config_path, sizeof(config_path), ".config/wlgol/keys.conf");
|
|
|
|
|
g_config = config_load(config_path);
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
redraw();
|
|
|
|
|
|
|
|
|
|
while (wl_display_dispatch(display) != -1 && running)
|
|
|
|
|
{
|
|
|
|
|
// nothing inside
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xdg_toplevel_destroy(xdg_toplevel);
|
|
|
|
|
xdg_surface_destroy(xdg_surface);
|
|
|
|
|
wl_surface_destroy(canvas.surface);
|
2026-11-08 14:46:04 +01:00
|
|
|
if (canvas.pool) wl_shm_pool_destroy(canvas.pool);
|
|
|
|
|
if (canvas.buffer) wl_buffer_destroy(canvas.buffer);
|
2026-06-13 10:22:08 +02:00
|
|
|
wl_keyboard_destroy(keyboard_data.keyboard);
|
|
|
|
|
if (keyboard_data.xkb_state != NULL)
|
|
|
|
|
{
|
|
|
|
|
xkb_state_unref(keyboard_data.xkb_state);
|
|
|
|
|
}
|
|
|
|
|
if (keyboard_data.xkb_keymap != NULL)
|
|
|
|
|
{
|
|
|
|
|
xkb_keymap_unref(keyboard_data.xkb_keymap);
|
|
|
|
|
}
|
|
|
|
|
if (keyboard_data.xkb_context != NULL)
|
|
|
|
|
{
|
|
|
|
|
xkb_context_unref(keyboard_data.xkb_context);
|
|
|
|
|
}
|
2024-06-02 19:29:26 +02:00
|
|
|
wl_registry_destroy(registry);
|
2024-06-02 19:29:26 +02:00
|
|
|
wl_display_disconnect(display);
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned char mycolour = 0;
|
|
|
|
|
char direction = 1;
|
|
|
|
|
int framecounter = 0;
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
static void fill_rect(int x0, int y0, int w, int h, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
|
|
|
if (w <= 0 || h <= 0) return;
|
|
|
|
|
for (int y = y0; y < y0 + h && y < canvas.height; y++) {
|
|
|
|
|
if (y < 0) continue;
|
|
|
|
|
for (int x = x0; x < x0 + w && x < canvas.width; x++) {
|
|
|
|
|
if (x < 0) continue;
|
|
|
|
|
struct pixel {
|
2024-06-02 19:29:26 +02:00
|
|
|
unsigned char blue;
|
|
|
|
|
unsigned char green;
|
|
|
|
|
unsigned char red;
|
|
|
|
|
unsigned char alpha;
|
|
|
|
|
} *px = (struct pixel *)(canvas.data + y * canvas.stride + x * 4);
|
2026-11-08 14:46:04 +01:00
|
|
|
px->red = r;
|
|
|
|
|
px->green = g;
|
|
|
|
|
px->blue = b;
|
|
|
|
|
px->alpha = a;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-02 19:29:26 +02:00
|
|
|
|
2026-11-08 15:47:46 +01:00
|
|
|
static int count_neighbors(int c, int r) {
|
|
|
|
|
int n = 0;
|
|
|
|
|
for (int dr = -1; dr <= 1; dr++) {
|
|
|
|
|
for (int dc = -1; dc <= 1; dc++) {
|
|
|
|
|
if (dr == 0 && dc == 0) continue;
|
|
|
|
|
n += grid_get(&grid, c + dc, r + dr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void simulation_tick(void) {
|
|
|
|
|
if (grid_back.cols != grid.cols || grid_back.rows != grid.rows) {
|
|
|
|
|
grid_free(&grid_back);
|
|
|
|
|
grid_init(&grid_back, grid.cols, grid.rows);
|
|
|
|
|
}
|
|
|
|
|
grid_clear(&grid_back);
|
|
|
|
|
for (int r = 0; r < grid.rows; r++) {
|
|
|
|
|
for (int c = 0; c < grid.cols; c++) {
|
|
|
|
|
int alive = grid_get(&grid, c, r);
|
|
|
|
|
int nb = count_neighbors(c, r);
|
|
|
|
|
int next = 0;
|
|
|
|
|
if (alive && (nb == 2 || nb == 3)) next = 1;
|
|
|
|
|
if (!alive && nb == 3) next = 1;
|
|
|
|
|
grid_set(&grid_back, c, r, next);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* swap cell buffers */
|
|
|
|
|
unsigned char *tmp = grid.cells;
|
|
|
|
|
grid.cells = grid_back.cells;
|
|
|
|
|
grid_back.cells = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
void redraw()
|
|
|
|
|
{
|
|
|
|
|
if (need_resize) {
|
|
|
|
|
canvas_resize(logical_width, logical_height, current_scale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clock_t start = clock();
|
|
|
|
|
|
|
|
|
|
/* update cursor from pointer */
|
|
|
|
|
cursor_x = pointer_data.x;
|
|
|
|
|
cursor_y = pointer_data.y;
|
|
|
|
|
|
2026-11-08 15:47:46 +01:00
|
|
|
/* tick timing */
|
|
|
|
|
struct timespec now;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
if (!sim_time_initialized) {
|
|
|
|
|
sim_last_time = now;
|
|
|
|
|
sim_time_initialized = 1;
|
|
|
|
|
}
|
|
|
|
|
double dt = (now.tv_sec - sim_last_time.tv_sec) + (now.tv_nsec - sim_last_time.tv_nsec) / 1e9;
|
|
|
|
|
sim_last_time = now;
|
|
|
|
|
if (!sim_paused) {
|
|
|
|
|
sim_accumulator += dt;
|
|
|
|
|
while (sim_accumulator >= sim_interval) {
|
|
|
|
|
simulation_tick();
|
|
|
|
|
sim_accumulator -= sim_interval;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* paused-only editing */
|
|
|
|
|
if (sim_paused && active_tool < 0) {
|
|
|
|
|
if (mouse_left_down || mouse_right_down) {
|
|
|
|
|
int cx = (int)(cursor_x / 8.0);
|
|
|
|
|
int cy = (int)(cursor_y / 8.0);
|
|
|
|
|
if (cx >= 0 && cy >= 0 && cx < grid.cols && cy < grid.rows) {
|
|
|
|
|
if (mouse_left_down) grid_set(&grid, cx, cy, 1);
|
|
|
|
|
if (mouse_right_down) grid_set(&grid, cx, cy, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-08 14:46:04 +01:00
|
|
|
/* handle pending click placement */
|
|
|
|
|
if (click_pending) {
|
|
|
|
|
click_pending = 0;
|
|
|
|
|
if (active_tool >= 0) {
|
|
|
|
|
int cx = (int)(click_x / 8.0);
|
|
|
|
|
int cy = (int)(click_y / 8.0);
|
|
|
|
|
int dx[16], dy[16];
|
|
|
|
|
int n = pattern_rotated_offsets(active_tool, active_rotation, dx, dy);
|
|
|
|
|
int ok = 1;
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
int c = cx + dx[i];
|
|
|
|
|
int r = cy + dy[i];
|
|
|
|
|
if (c < 0 || r < 0 || c >= grid.cols || r >= grid.rows) { ok = 0; break; }
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
if (ok) {
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
grid_set(&grid, cx + dx[i], cy + dy[i], 1);
|
|
|
|
|
}
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cell_px = (grid.cols > 0) ? (canvas.width / grid.cols) : 8;
|
|
|
|
|
if (cell_px < 1) cell_px = 1;
|
|
|
|
|
|
|
|
|
|
/* clear background */
|
|
|
|
|
fill_rect(0, 0, canvas.width, canvas.height, 20, 20, 24, 255);
|
|
|
|
|
|
|
|
|
|
/* grid lines */
|
|
|
|
|
if (show_grid_lines && grid.show_lines) {
|
|
|
|
|
for (int c = 0; c <= grid.cols; c++) {
|
|
|
|
|
int x = c * cell_px;
|
|
|
|
|
fill_rect(x, 0, 1, canvas.height, 40, 40, 48, 255);
|
|
|
|
|
}
|
|
|
|
|
for (int r = 0; r <= grid.rows; r++) {
|
|
|
|
|
int y = r * cell_px;
|
|
|
|
|
fill_rect(0, y, canvas.width, 1, 40, 40, 48, 255);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* alive cells */
|
|
|
|
|
for (int r = 0; r < grid.rows; r++) {
|
|
|
|
|
for (int c = 0; c < grid.cols; c++) {
|
|
|
|
|
if (grid_get(&grid, c, r)) {
|
|
|
|
|
fill_rect(c * cell_px + 1, r * cell_px + 1, cell_px - 2, cell_px - 2, 80, 200, 120, 255);
|
2024-06-02 19:29:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-08 14:46:04 +01:00
|
|
|
|
|
|
|
|
/* preview */
|
|
|
|
|
if (active_tool >= 0) {
|
|
|
|
|
int cx = (int)(cursor_x / 8.0);
|
|
|
|
|
int cy = (int)(cursor_y / 8.0);
|
|
|
|
|
int dx[16], dy[16];
|
|
|
|
|
int n = pattern_rotated_offsets(active_tool, active_rotation, dx, dy);
|
|
|
|
|
int ok = 1;
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
int c = cx + dx[i];
|
|
|
|
|
int r = cy + dy[i];
|
|
|
|
|
if (c < 0 || r < 0 || c >= grid.cols || r >= grid.rows) { ok = 0; break; }
|
|
|
|
|
}
|
|
|
|
|
unsigned char pr = ok ? 220 : 220;
|
|
|
|
|
unsigned char pg = ok ? 200 : 80;
|
|
|
|
|
unsigned char pb = ok ? 80 : 80;
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
int c = cx + dx[i];
|
|
|
|
|
int r = cy + dy[i];
|
|
|
|
|
fill_rect(c * cell_px + 2, r * cell_px + 2, cell_px - 4, cell_px - 4, pr, pg, pb, 160);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
// callback
|
|
|
|
|
canvas.frame = wl_surface_frame(canvas.surface);
|
|
|
|
|
wl_callback_add_listener(canvas.frame, &wl_surface_frame_listener, NULL);
|
|
|
|
|
wl_surface_attach(canvas.surface, canvas.buffer, 0, 0);
|
|
|
|
|
wl_surface_damage(canvas.surface, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
wl_surface_commit(canvas.surface);
|
|
|
|
|
|
|
|
|
|
framecounter++;
|
|
|
|
|
|
|
|
|
|
struct timespec ntime;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ntime);
|
|
|
|
|
double elapsed_time = (ntime.tv_sec - mytime.tv_sec) + (ntime.tv_nsec - mytime.tv_nsec) / 1e9;
|
|
|
|
|
|
2024-06-02 19:29:26 +02:00
|
|
|
if (elapsed_time > 1.0)
|
|
|
|
|
{
|
2024-06-02 19:29:26 +02:00
|
|
|
clock_t end = clock();
|
2024-06-02 19:29:26 +02:00
|
|
|
double dur = ((double)end - start) / CLOCKS_PER_SEC;
|
2024-06-02 19:29:26 +02:00
|
|
|
printf("FPS: %d in %g sec\n", framecounter, dur);
|
|
|
|
|
mytime = ntime;
|
|
|
|
|
framecounter = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|