#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct wl_compositor *compositor; struct wl_shm *shm; struct xdg_wm_base *xdg_shell; struct wl_seat *wl_seat; struct wl_output *wl_output = NULL; int output_scale_factor = 1; int output_width = 0; int output_height = 0; struct keyboard_data keyboard_data; struct pointer_data pointer_data; int running = 1; int released = 0; 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; 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; double cursor_x = 0, cursor_y = 0; int click_pending = 0; double click_x = 0, click_y = 0; int show_grid_lines = 1; struct timespec mytime; struct canvas { int width; /* physical */ int height; /* physical */ int stride; int size; struct wl_buffer *buffer; unsigned char *data; struct wl_shm_pool *pool; int fd; struct wl_surface *surface; struct wl_callback *frame; } canvas; 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; } 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; } } 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, }; // 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); } 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); } } void registry_global_remove_handler( void *data, struct wl_registry *registry, uint32_t name) { // nothing } 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); if (width > 0) logical_width = width; if (height > 0) logical_height = height; need_resize = 1; } 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}; void wl_buffer_release_handler(void *data, struct wl_buffer *wl_buffer) { // printf("wl_buffer_release\n"); released = 1; } const struct wl_buffer_listener wl_buffer_listener = { .release = wl_buffer_release_handler}; void redraw(); // forward declaration static void simulation_tick(void); static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t mods, void *user_data) { (void)key; (void)user_data; 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: running = 0; 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_SELECT_BEACON: active_tool = 4; active_rotation = 0; printf("selected beacon\n"); break; case ACTION_SELECT_BOAT: active_tool = 5; active_rotation = 0; printf("selected boat\n"); break; case ACTION_SELECT_TUB: active_tool = 6; active_rotation = 0; printf("selected tub\n"); break; case ACTION_SELECT_BEEHIVE: active_tool = 7; active_rotation = 0; printf("selected beehive\n"); break; case ACTION_SELECT_RPENTOMINO: active_tool = 8; active_rotation = 0; printf("selected rpentomino\n"); break; case ACTION_SELECT_ACORN: active_tool = 9; active_rotation = 0; printf("selected acorn\n"); break; case ACTION_CLEAR: printf("clear requested\n"); break; case ACTION_PAUSE: sim_paused = !sim_paused; sim_accumulator = 0; printf("paused=%d\n", sim_paused); break; case ACTION_STEP: if (sim_paused) { simulation_tick(); printf("stepped\n"); } break; case ACTION_SPEED_UP: sim_interval /= 2.0; if (sim_interval < 0.025) sim_interval = 0.025; printf("interval=%g\n", sim_interval); break; case ACTION_SPEED_DOWN: sim_interval *= 2.0; if (sim_interval > 1.0) sim_interval = 1.0; printf("interval=%g\n", sim_interval); break; case ACTION_TOGGLE_GRID: show_grid_lines = !show_grid_lines; break; default: break; } } static void handle_button(uint32_t button, uint32_t state, double x, double y, void *user_data) { (void)user_data; 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); } void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time) { if (canvas.frame) { wl_callback_destroy(canvas.frame); canvas.frame = NULL; } if (released) { redraw(); released = 0; } } const struct wl_callback_listener wl_surface_frame_listener = { .done = wl_callack_done}; 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); grid_resize(&grid_back, grid.cols, grid.rows); 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); } 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); 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); } 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); keyboard_data.keyboard = wl_seat_get_keyboard(wl_seat); keyboard_set_handler(&keyboard_data, handle_key, NULL); wl_keyboard_add_listener(keyboard_data.keyboard, &keyboard_listener, &keyboard_data); pointer_init_cursor(&pointer_data, compositor, shm); pointer_set_button_handler(&pointer_data, handle_button, NULL); pointer_data.pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(pointer_data.pointer, &pointer_listener, &pointer_data); canvas.fd = -1; logical_width = 1024; logical_height = 576; grid_init(&grid, logical_width / 8, logical_height / 8); grid.show_lines = 1; // wait for the surface to be configured wl_display_roundtrip(display); 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; /* 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); 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); if (canvas.pool) wl_shm_pool_destroy(canvas.pool); if (canvas.buffer) wl_buffer_destroy(canvas.buffer); 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); } wl_registry_destroy(registry); wl_display_disconnect(display); } unsigned char mycolour = 0; char direction = 1; int framecounter = 0; 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 { unsigned char blue; unsigned char green; unsigned char red; unsigned char alpha; } *px = (struct pixel *)(canvas.data + y * canvas.stride + x * 4); px->red = r; px->green = g; px->blue = b; px->alpha = a; } } } 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; } 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; /* 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); } } } /* 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; } } if (ok) { for (int i = 0; i < n; i++) { grid_set(&grid, cx + dx[i], cy + dy[i], 1); } } } } 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); } } } /* 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); } } // 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; if (elapsed_time > 1.0) { clock_t end = clock(); double dur = ((double)end - start) / CLOCKS_PER_SEC; printf("FPS: %d in %g sec\n", framecounter, dur); mytime = ntime; framecounter = 0; } }