feat: implement keyboard and mouse input, pattern placement, and HiDPI scaling

Add configurable key bindings (TOML), modifier-aware dispatch, mouse-driven
pattern placement with preview and rotation, cell grid storage/rendering,
and fractional/integer HiDPI scaling. Archive the input-and-placement change
and sync its specs to main specs.

Tests: Hashtable, Config, Grid, Pattern (4/4 pass).
This commit is contained in:
2026-11-08 14:46:04 +01:00
parent 5e7abfb56b
commit 7874a5f7c6
31 changed files with 4081 additions and 96 deletions
+91
View File
@@ -25,15 +25,49 @@ add_custom_command(
DEPENDS ${CMAKE_SOURCE_DIR}/xdg-shell.xml DEPENDS ${CMAKE_SOURCE_DIR}/xdg-shell.xml
) )
# viewporter
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/viewporter.h
COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/viewporter.xml ${CMAKE_CURRENT_BINARY_DIR}/include/viewporter.h
DEPENDS ${CMAKE_SOURCE_DIR}/viewporter.xml
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/viewporter.c
COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/viewporter.xml ${CMAKE_CURRENT_BINARY_DIR}/viewporter.c
DEPENDS ${CMAKE_SOURCE_DIR}/viewporter.xml
)
# fractional-scale-v1
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/fractional-scale-v1.h
COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/fractional-scale-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/include/fractional-scale-v1.h
DEPENDS ${CMAKE_SOURCE_DIR}/fractional-scale-v1.xml
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/fractional-scale-v1.c
COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/fractional-scale-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/fractional-scale-v1.c
DEPENDS ${CMAKE_SOURCE_DIR}/fractional-scale-v1.xml
)
set(SOURCES set(SOURCES
tkeyboard.c tkeyboard.c
tpointer.c tpointer.c
hashtable.c hashtable.c
toml.c
config.c
grid.c
pattern.c
${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c
${CMAKE_CURRENT_BINARY_DIR}/viewporter.c
${CMAKE_CURRENT_BINARY_DIR}/fractional-scale-v1.c
) )
set(GENHEADERS set(GENHEADERS
${CMAKE_CURRENT_BINARY_DIR}/include/xdg-shell.h ${CMAKE_CURRENT_BINARY_DIR}/include/xdg-shell.h
${CMAKE_CURRENT_BINARY_DIR}/include/viewporter.h
${CMAKE_CURRENT_BINARY_DIR}/include/fractional-scale-v1.h
) )
add_executable(wlgol wlgol.c ${SOURCES} ${GENHEADERS}) add_executable(wlgol wlgol.c ${SOURCES} ${GENHEADERS})
@@ -47,6 +81,7 @@ target_include_directories(wlgol
target_link_libraries(wlgol target_link_libraries(wlgol
wayland-client wayland-client
wayland-cursor wayland-cursor
m
${XKBCOMMON_LIBRARIES} ${XKBCOMMON_LIBRARIES}
) )
@@ -75,3 +110,59 @@ target_compile_options(test_hashtable PRIVATE
enable_testing() enable_testing()
add_test(NAME Hashtable COMMAND test_hashtable) add_test(NAME Hashtable COMMAND test_hashtable)
add_executable(test_config tests/test_config.c
${SOURCES}
)
target_link_libraries(test_config
wayland-client
wayland-cursor
${XKBCOMMON_LIBRARIES}
)
target_include_directories(test_config
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${XKBCOMMON_INCLUDE_DIRS}
)
target_compile_options(test_config PRIVATE
${XKBCOMMON_CFLAGS_OTHER}
)
add_test(NAME Config COMMAND test_config)
add_executable(test_grid tests/test_grid.c
${SOURCES}
)
target_link_libraries(test_grid
wayland-client
wayland-cursor
m
${XKBCOMMON_LIBRARIES}
)
target_include_directories(test_grid
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${XKBCOMMON_INCLUDE_DIRS}
)
target_compile_options(test_grid PRIVATE
${XKBCOMMON_CFLAGS_OTHER}
)
add_test(NAME Grid COMMAND test_grid)
add_executable(test_pattern tests/test_pattern.c
${SOURCES}
)
target_link_libraries(test_pattern
wayland-client
wayland-cursor
m
${XKBCOMMON_LIBRARIES}
)
target_include_directories(test_pattern
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${XKBCOMMON_INCLUDE_DIRS}
)
target_compile_options(test_pattern PRIVATE
${XKBCOMMON_CFLAGS_OTHER}
)
add_test(NAME Pattern COMMAND test_pattern)
+229
View File
@@ -0,0 +1,229 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libgen.h>
#include "toml.h"
static char *trim(char *s) {
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
char *end = s + strlen(s);
while (end > s && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) end--;
*end = '\0';
return s;
}
static void config_ensure_capacity(config_t *cfg) {
if (cfg->count < cfg->capacity) return;
int newcap = cfg->capacity ? cfg->capacity * 2 : 16;
binding_t *nb = realloc(cfg->items, newcap * sizeof(binding_t));
if (!nb) return;
cfg->items = nb;
cfg->capacity = newcap;
}
static void config_set_binding(config_t *cfg, uint32_t mods, xkb_keysym_t keysym, action_t action) {
for (int i = 0; i < cfg->count; i++) {
if (cfg->items[i].mods == mods && cfg->items[i].keysym == keysym) {
cfg->items[i].action = action;
return;
}
}
config_ensure_capacity(cfg);
if (cfg->count >= cfg->capacity) return;
cfg->items[cfg->count].mods = mods;
cfg->items[cfg->count].keysym = keysym;
cfg->items[cfg->count].action = action;
cfg->count++;
}
int config_parse_mods(const char *s, uint32_t *out_mods, char **out_key) {
if (!s || !*s || !out_mods || !out_key) return -1;
char *copy = strdup(s);
if (!copy) return -1;
/* count tokens */
int n = 0;
char *p = copy;
/* first pass count */
char *tmp = strdup(s);
char *save = NULL;
char *t = strtok_r(tmp, "+", &save);
/* collect */
char **toks = NULL;
int cap = 0;
while (t) {
char *tt = trim(t);
if (*tt) {
if (n == cap) {
cap = cap ? cap * 2 : 4;
toks = realloc(toks, cap * sizeof(char *));
}
toks[n++] = strdup(tt);
}
t = strtok_r(NULL, "+", &save);
}
free(tmp);
if (n == 0) { free(copy); free(toks); return -1; }
uint32_t mods = 0;
for (int i = 0; i < n - 1; i++) {
if (strcasecmp(toks[i], "ctrl") == 0 || strcasecmp(toks[i], "control") == 0) mods |= MOD_CTRL;
else if (strcasecmp(toks[i], "shift") == 0) mods |= MOD_SHIFT;
else if (strcasecmp(toks[i], "alt") == 0) mods |= MOD_ALT;
else if (strcasecmp(toks[i], "super") == 0 || strcasecmp(toks[i], "meta") == 0 || strcasecmp(toks[i], "win") == 0) mods |= MOD_SUPER;
else {
/* unknown modifier -> fail */
for (int j = 0; j < n; j++) free(toks[j]);
free(toks);
free(copy);
return -1;
}
free(toks[i]);
}
*out_mods = mods;
*out_key = toks[n - 1];
/* free remaining token strings except key (transferred) */
for (int i = 0; i < n - 1; i++) { /* already freed */ }
free(toks);
free(copy);
return 0;
}
action_t config_parse_action(const char *s) {
if (!s) return ACTION_NONE;
char buf[128];
snprintf(buf, sizeof(buf), "%s", s);
trim(buf);
if (strcmp(buf, "quit") == 0) return ACTION_QUIT;
if (strcmp(buf, "cancel") == 0) return ACTION_CANCEL;
if (strcmp(buf, "rotate") == 0) return ACTION_ROTATE;
if (strcmp(buf, "clear") == 0) return ACTION_CLEAR;
if (strcmp(buf, "pause") == 0) return ACTION_PAUSE;
if (strcmp(buf, "step") == 0) return ACTION_STEP;
if (strcmp(buf, "speed_up") == 0) return ACTION_SPEED_UP;
if (strcmp(buf, "speed_down") == 0) return ACTION_SPEED_DOWN;
if (strcmp(buf, "toggle_grid") == 0) return ACTION_TOGGLE_GRID;
if (strncmp(buf, "select:", 7) == 0) {
const char *pat = buf + 7;
if (strcmp(pat, "glider") == 0) return ACTION_SELECT_GLIDER;
if (strcmp(pat, "blinker") == 0) return ACTION_SELECT_BLINKER;
if (strcmp(pat, "block") == 0) return ACTION_SELECT_BLOCK;
if (strcmp(pat, "toad") == 0) return ACTION_SELECT_TOAD;
}
return ACTION_NONE;
}
config_t config_defaults(void) {
config_t cfg = {0};
struct { const char *key; action_t action; } defs[] = {
{"1", ACTION_SELECT_GLIDER},
{"2", ACTION_SELECT_BLINKER},
{"3", ACTION_SELECT_BLOCK},
{"4", ACTION_SELECT_TOAD},
{"space", ACTION_ROTATE},
{"escape", ACTION_CANCEL},
{"ctrl+q", ACTION_QUIT},
};
int ndef = sizeof(defs) / sizeof(defs[0]);
for (int i = 0; i < ndef; i++) {
uint32_t mods = 0;
char *key = NULL;
if (config_parse_mods(defs[i].key, &mods, &key) != 0) continue;
xkb_keysym_t sym = xkb_keysym_from_name(key, XKB_KEYSYM_CASE_INSENSITIVE);
free(key);
if (sym == XKB_KEY_NoSymbol) continue;
config_set_binding(&cfg, mods, sym, defs[i].action);
}
return cfg;
}
static const char *default_file_content =
"# wlgol key bindings\n"
"[bindings]\n"
"\"1\" = \"select:glider\"\n"
"\"2\" = \"select:blinker\"\n"
"\"3\" = \"select:block\"\n"
"\"4\" = \"select:toad\"\n"
"\"space\" = \"rotate\"\n"
"\"escape\" = \"cancel\"\n"
"\"ctrl+q\" = \"quit\"\n";
int config_ensure_default_file(const char *path) {
if (!path) return -1;
struct stat st;
if (stat(path, &st) == 0) return 0;
char *parent = strdup(path);
char *grand = strdup(path);
dirname(parent);
/* parent of parent */
char *p2 = strdup(parent);
dirname(p2);
mkdir(p2, 0755);
mkdir(parent, 0755);
free(p2);
FILE *fp = fopen(path, "w");
if (!fp) { free(parent); free(grand); return -1; }
fputs(default_file_content, fp);
fclose(fp);
free(parent);
free(grand);
return 0;
}
config_t config_load(const char *path) {
config_t cfg = config_defaults();
config_ensure_default_file(path);
FILE *fp = fopen(path, "r");
if (!fp) return cfg;
char errbuf[512];
toml_table_t *tab = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if (!tab) return cfg;
toml_table_t *bindings = toml_table_in(tab, "bindings");
if (bindings) {
int n = toml_table_nkval(bindings);
for (int i = 0; i < n; i++) {
const char *keystr = toml_key_in(bindings, i);
if (!keystr) break;
toml_datum_t d = toml_string_in(bindings, keystr);
if (!d.ok) { if (d.u.s) free(d.u.s); continue; }
uint32_t mods = 0;
char *key = NULL;
if (config_parse_mods(keystr, &mods, &key) != 0) { free(d.u.s); continue; }
xkb_keysym_t sym = xkb_keysym_from_name(key, XKB_KEYSYM_CASE_INSENSITIVE);
free(key);
if (sym == XKB_KEY_NoSymbol) { free(d.u.s); continue; }
action_t act = config_parse_action(d.u.s);
free(d.u.s);
if (act == ACTION_NONE) continue;
config_set_binding(&cfg, mods, sym, act);
}
}
toml_free(tab);
return cfg;
}
void config_free(config_t *cfg) {
if (!cfg) return;
free(cfg->items);
cfg->items = NULL;
cfg->count = 0;
cfg->capacity = 0;
}
action_t config_lookup(const config_t *cfg, uint32_t mods, xkb_keysym_t keysym) {
if (!cfg) return ACTION_NONE;
for (int i = 0; i < cfg->count; i++) {
if (cfg->items[i].mods == mods && cfg->items[i].keysym == keysym) {
return cfg->items[i].action;
}
}
return ACTION_NONE;
}
+102
View File
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fractional_scale_v1">
<copyright>
Copyright © 2022 Kenny Levinsen
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for requesting fractional surface scales">
This protocol allows a compositor to suggest for surfaces to render at
fractional scales.
A client can submit scaled content by utilizing wp_viewport. This is done by
creating a wp_viewport object for the surface and setting the destination
rectangle to the surface size before the scale factor is applied.
The buffer size is calculated by multiplying the surface size by the
intended scale.
The wl_surface buffer scale should remain set to 1.
If a surface has a surface-local size of 100 px by 50 px and wishes to
submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
be used and the wp_viewport destination rectangle should be 100 px by 50 px.
For toplevel surfaces, the size is rounded halfway away from zero. The
rounding algorithm for subsurface position and size is not defined.
</description>
<interface name="wp_fractional_scale_manager_v1" version="1">
<description summary="fractional surface scale information">
A global interface for requesting surfaces to use fractional scales.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the fractional surface scale interface">
Informs the server that the client will not be using this protocol
object anymore. This does not affect any other objects,
wp_fractional_scale_v1 objects included.
</description>
</request>
<enum name="error">
<entry name="fractional_scale_exists" value="0"
summary="the surface already has a fractional_scale object associated"/>
</enum>
<request name="get_fractional_scale">
<description summary="extend surface interface for scale information">
Create an add-on object for the the wl_surface to let the compositor
request fractional scales. If the given wl_surface already has a
wp_fractional_scale_v1 object associated, the fractional_scale_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_fractional_scale_v1"
summary="the new surface scale info interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_fractional_scale_v1" version="1">
<description summary="fractional scale interface to a wl_surface">
An additional interface to a wl_surface object which allows the compositor
to inform the client of the preferred scale.
</description>
<request name="destroy" type="destructor">
<description summary="remove surface scale information for surface">
Destroy the fractional scale object. When this object is destroyed,
preferred_scale events will no longer be sent.
</description>
</request>
<event name="preferred_scale">
<description summary="notify of new preferred scale">
Notification of a new preferred scale for this surface that the
compositor suggests that the client should use.
The sent scale is the numerator of a fraction with a denominator of 120.
</description>
<arg name="scale" type="uint" summary="the new preferred scale"/>
</event>
</interface>
</protocol>
+58
View File
@@ -0,0 +1,58 @@
#include "grid.h"
#include <stdlib.h>
#include <string.h>
void grid_init(grid_t *g, int cols, int rows) {
if (!g) return;
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
g->cols = cols;
g->rows = rows;
g->cells = calloc((size_t)cols * (size_t)rows, 1);
g->show_lines = 1;
}
void grid_free(grid_t *g) {
if (!g) return;
free(g->cells);
g->cells = NULL;
g->cols = 0;
g->rows = 0;
}
void grid_resize(grid_t *g, int new_cols, int new_rows) {
if (!g) return;
if (new_cols < 1) new_cols = 1;
if (new_rows < 1) new_rows = 1;
unsigned char *ncells = calloc((size_t)new_cols * (size_t)new_rows, 1);
if (!ncells) return;
int copy_cols = g->cols < new_cols ? g->cols : new_cols;
int copy_rows = g->rows < new_rows ? g->rows : new_rows;
for (int r = 0; r < copy_rows; r++) {
memcpy(ncells + (size_t)r * new_cols,
g->cells + (size_t)r * g->cols,
(size_t)copy_cols);
}
free(g->cells);
g->cells = ncells;
g->cols = new_cols;
g->rows = new_rows;
}
void grid_clear(grid_t *g) {
if (!g || !g->cells) return;
memset(g->cells, 0, (size_t)g->cols * (size_t)g->rows);
}
int grid_get(const grid_t *g, int c, int r) {
if (!g || !g->cells) return 0;
if (c < 0 || r < 0 || c >= g->cols || r >= g->rows) return 0;
return g->cells[(size_t)r * g->cols + c];
}
void grid_set(grid_t *g, int c, int r, int val) {
if (!g || !g->cells) return;
if (c < 0 || r < 0 || c >= g->cols || r >= g->rows) return;
g->cells[(size_t)r * g->cols + c] = val ? 1 : 0;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stdint.h>
#include <xkbcommon/xkbcommon.h>
#define MOD_CTRL (1u << 0)
#define MOD_SHIFT (1u << 1)
#define MOD_ALT (1u << 2)
#define MOD_SUPER (1u << 3)
typedef enum {
ACTION_NONE = 0,
ACTION_QUIT,
ACTION_CANCEL,
ACTION_ROTATE,
ACTION_SELECT_GLIDER,
ACTION_SELECT_BLINKER,
ACTION_SELECT_BLOCK,
ACTION_SELECT_TOAD,
ACTION_CLEAR,
ACTION_PAUSE,
ACTION_STEP,
ACTION_SPEED_UP,
ACTION_SPEED_DOWN,
ACTION_TOGGLE_GRID,
} action_t;
typedef struct {
uint32_t mods;
xkb_keysym_t keysym;
action_t action;
} binding_t;
typedef struct {
binding_t *items;
int count;
int capacity;
} config_t;
/* Built-in defaults. */
config_t config_defaults(void);
/* Ensure config file exists; create with defaults if missing. Returns 0 on success. */
int config_ensure_default_file(const char *path);
/* Load config from path, merging over defaults. */
config_t config_load(const char *path);
void config_free(config_t *cfg);
/* Lookup action for a key press. Returns ACTION_NONE if no binding. */
action_t config_lookup(const config_t *cfg, uint32_t mods, xkb_keysym_t keysym);
/* Helpers exposed for tests. */
int config_parse_mods(const char *s, uint32_t *out_mods, char **out_key);
action_t config_parse_action(const char *s);
#endif
+18
View File
@@ -0,0 +1,18 @@
#ifndef GRID_H
#define GRID_H
typedef struct {
int cols;
int rows;
unsigned char *cells;
int show_lines;
} grid_t;
void grid_init(grid_t *g, int cols, int rows);
void grid_free(grid_t *g);
void grid_resize(grid_t *g, int new_cols, int new_rows);
void grid_clear(grid_t *g);
int grid_get(const grid_t *g, int c, int r);
void grid_set(grid_t *g, int c, int r, int val);
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef PATTERN_H
#define PATTERN_H
typedef struct { int dx, dy; } cell_offset_t;
typedef struct {
const char *name;
int count;
const cell_offset_t *cells;
} pattern_t;
int pattern_count(void);
const pattern_t *pattern_get(int index);
/* Fill out_dx/out_dy with normalized rotated offsets. Returns cell count. */
int pattern_rotated_offsets(int index, int rotation, int *out_dx, int *out_dy);
/* Bounding box of rotated pattern. */
void pattern_bounds(int index, int rotation, int *w, int *h);
#endif
+1 -1
View File
@@ -5,7 +5,7 @@
#include <wayland-client.h> #include <wayland-client.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
typedef void (*keyboard_key_handler_t)(uint32_t key, uint32_t state, xkb_keysym_t sym, void *user_data); typedef void (*keyboard_key_handler_t)(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t mods, void *user_data);
struct keyboard_data { struct keyboard_data {
struct wl_keyboard *keyboard; struct wl_keyboard *keyboard;
+175
View File
@@ -0,0 +1,175 @@
/*
MIT License
Copyright (c) CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef TOML_H
#define TOML_H
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */
char *errbuf, int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t *tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char *z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t *ts; /* ts must be freed after use */
char *s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t *arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx);
TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx);
/* ... returns 1 if key exists in tab, 0 otherwise */
TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr,
const char *key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr,
const char *key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr,
const char *key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab,
const char *key);
TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab,
const char *key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */
TOML_EXTERN char toml_array_kind(const toml_array_t *arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t *arr);
/* Return the key of an array */
TOML_EXTERN const char *toml_array_key(const toml_array_t *arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t *tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t *tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t *tab);
/* Return the key of a table*/
TOML_EXTERN const char *toml_table_key(const toml_table_t *tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t),
void (*xxfree)(void *));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char *toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret);
#endif /* TOML_H */
+10
View File
@@ -5,6 +5,8 @@
#include <wayland-cursor.h> #include <wayland-cursor.h>
typedef void (*pointer_button_handler_t)(uint32_t button, uint32_t state, double x, double y, void *user_data);
struct pointer_data { struct pointer_data {
struct wl_cursor_theme *cursor_theme; struct wl_cursor_theme *cursor_theme;
struct wl_pointer *pointer; struct wl_pointer *pointer;
@@ -12,10 +14,18 @@ struct pointer_data {
struct wl_cursor *cursor; struct wl_cursor *cursor;
struct wl_cursor_image *cursor_image; struct wl_cursor_image *cursor_image;
struct wl_buffer *cursor_buffer; struct wl_buffer *cursor_buffer;
double x;
double y;
pointer_button_handler_t button_handler;
void *user_data;
}; };
extern const struct wl_pointer_listener pointer_listener; extern const struct wl_pointer_listener pointer_listener;
void pointer_init_cursor(struct pointer_data *pointer_data, struct wl_compositor *compositor, struct wl_shm *shm); void pointer_init_cursor(struct pointer_data *pointer_data, struct wl_compositor *compositor, struct wl_shm *shm);
void pointer_set_button_handler(struct pointer_data *pointer_data, pointer_button_handler_t handler, void *user_data);
#endif #endif
@@ -2,46 +2,46 @@
## 1. Protocol generation and build setup ## 1. Protocol generation and build setup
- [ ] 1.1 Vendor `viewporter.xml` and `fractional-scale-v1.xml` into the repo and add wayland-scanner custom commands in `CMakeLists.txt`, verifying headers generate under `build/include`. - [x] 1.1 Vendor `viewporter.xml` and `fractional-scale-v1.xml` into the repo and add wayland-scanner custom commands in `CMakeLists.txt`, verifying headers generate under `build/include`.
- [ ] 1.2 Add `tomlc99.c`/`tomlc99.h` to the source tree and CMake sources, verifying the project still configures and builds. - [x] 1.2 Add `tomlc99.c`/`tomlc99.h` (vendored as toml.c/toml.h) to the source tree and CMake sources, verifying the project still configures and builds.
## 2. Configuration module ## 2. Configuration module
- [ ] 2.1 Implement `config.c/h` to load or auto-create `~/.config/wlgol/keys.conf`, parse TOML bindings, and merge with built-in defaults, verifying a sample file round-trips. - [x] 2.1 Implement `config.c/h` to load or auto-create `~/.config/wlgol/keys.conf`, parse TOML bindings, and merge with built-in defaults, verifying a sample file round-trips.
- [ ] 2.2 Implement binding string parsing (modifiers + key aliases) and lookup by modifier mask and keysym, verifying unit tests cover merge and modifier matching. - [x] 2.2 Implement binding string parsing (modifiers + key aliases) and lookup by modifier mask and keysym, verifying unit tests cover merge and modifier matching.
## 3. Keyboard module ## 3. Keyboard module
- [ ] 3.1 Extend `keyboard_key_handler_t` to pass a modifier mask and update `tkeyboard.c` to compute it from xkb state, verifying compilation of all callers. - [x] 3.1 Extend `keyboard_key_handler_t` to pass a modifier mask and update `tkeyboard.c` to compute it from xkb state, verifying compilation of all callers.
- [ ] 3.2 Wire keyboard events to config-based binding lookup and action dispatch, verifying Escape and Ctrl+Q trigger their actions. - [x] 3.2 Wire keyboard events to config-based binding lookup and action dispatch, verifying Escape and Ctrl+Q trigger their actions.
## 4. Pointer module ## 4. Pointer module
- [ ] 4.1 Track logical cursor position in `tpointer.c/h` from motion events, verifying position updates on move. - [x] 4.1 Track logical cursor position in `tpointer.c/h` from motion events, verifying position updates on move.
- [ ] 4.2 Dispatch left-click events to the placement controller, verifying clicks are received. - [x] 4.2 Dispatch left-click events to the placement controller, verifying clicks are received.
## 5. Grid module ## 5. Grid module
- [ ] 5.1 Implement `grid.c/h` with storage, bounds checks, resize-with-preserve, clear, and toggle state, verifying unit tests for resize and bounds. - [x] 5.1 Implement `grid.c/h` with storage, bounds checks, resize-with-preserve, clear, and toggle state, verifying unit tests for resize and bounds.
- [ ] 5.2 Expose grid dimensions derived from logical surface size at 8 px per cell, verifying dimensions update on resize. - [x] 5.2 Expose grid dimensions derived from logical surface size at 8 px per cell, verifying dimensions update on resize.
## 6. Pattern module ## 6. Pattern module
- [ ] 6.1 Implement `pattern.c/h` with Glider, Blinker, Block, and Toad offsets and 90-degree rotation transform, verifying rotated offsets and bounds. - [x] 6.1 Implement `pattern.c/h` with Glider, Blinker, Block, and Toad offsets and 90-degree rotation transform, verifying rotated offsets and bounds.
- [ ] 6.2 Implement placement validity check (pattern fits within grid), verifying in-bounds and out-of-bounds cases. - [x] 6.2 Implement placement validity check (pattern fits within grid), verifying in-bounds and out-of-bounds cases.
## 7. Scaling module ## 7. Scaling module
- [ ] 7.1 Implement `scale.c/h` to bind fractional scale and viewporter, compute physical buffer size, and convert coordinates, verifying scale changes update buffer size. - [x] 7.1 Implement scaling (inlined in wlgol.c) to bind fractional scale and viewporter, compute physical buffer size, and convert coordinates, verifying scale changes update buffer size.
- [ ] 7.2 Implement integer-scale fallback via `wl_output`, verifying fallback path builds without fractional protocol. - [x] 7.2 Implement integer-scale fallback via `wl_output`, verifying fallback path builds without fractional protocol.
## 8. Rendering and application integration ## 8. Rendering and application integration
- [ ] 8.1 Refactor `wlgol.c` to use logical surface size, create scaled shm buffers, and render grid plus alive cells, verifying build succeeds. - [x] 8.1 Refactor `wlgol.c` to use logical surface size, create scaled shm buffers, and render grid plus alive cells, verifying build succeeds.
- [ ] 8.2 Add placement controller state machine (IDLE/ARMED), preview rendering, rotation, click placement, and cancellation, verifying behavior by manual run. - [x] 8.2 Add placement controller state machine (IDLE/ARMED), preview rendering, rotation, click placement, and cancellation, verifying behavior by manual run.
- [ ] 8.3 Integrate config loading, default file creation, and action dispatch into startup, verifying default config is generated. - [x] 8.3 Integrate config loading, default file creation, and action dispatch into startup, verifying default config is generated.
## 9. Verification ## 9. Verification
- [ ] 9.1 Build with CMake/Ninja and run `ctest --test-dir build`, verifying all tests pass. - [x] 9.1 Build with CMake/Ninja and run `ctest --test-dir build`, verifying all tests pass.
- [ ] 9.2 Run `./build/wlgol` under a Wayland compositor and manually verify placement, rotation, HiDPI scaling, and Esc cancellation. - [x] 9.2 Run `./build/wlgol` under a Wayland compositor and manually verify placement, rotation, HiDPI scaling, and Esc cancellation.
+61
View File
@@ -0,0 +1,61 @@
# cell-grid Specification
## Purpose
Stores and renders the Game of Life cell grid with a fixed cell size and wall boundaries, independent of future simulation rules.
## Requirements
### Requirement: Grid stores alive and dead cells
The system SHALL maintain a grid of cells, each either alive or dead, addressable by column and row.
#### Scenario: Cell access
- **WHEN** a cell is set to alive
- **THEN** reading that cell returns alive
- **AND** cells never set remain dead
### Requirement: Boundary cells are treated as dead
The system SHALL treat any cell outside the grid bounds as dead and shall not store state outside the grid.
#### Scenario: Out of bounds read
- **WHEN** a cell position outside the grid is queried
- **THEN** the result is dead
### Requirement: Grid resizes while preserving overlapping cells
The system SHALL preserve cells that remain within bounds when the grid dimensions change, clearing only cells that fall outside the new bounds.
#### Scenario: Enlarge grid
- **WHEN** the grid grows in either dimension
- **THEN** previously alive cells within the old bounds remain alive
#### Scenario: Shrink grid
- **WHEN** the grid shrinks in either dimension
- **THEN** cells outside the new bounds are discarded
- **AND** remaining cells keep their state
### Requirement: Cell size is fixed in logical pixels
The system SHALL use a cell size of 8 logical pixels, so grid dimensions derive from the logical surface size.
#### Scenario: Dimension calculation
- **WHEN** the logical surface is 400 pixels wide
- **THEN** the grid has 50 columns
### Requirement: Alive cells are rendered
The system SHALL render alive cells distinctly from dead cells.
#### Scenario: Render alive cell
- **WHEN** a cell is alive
- **THEN** it is drawn with the alive cell color
### Requirement: Grid can be cleared
The system SHALL set all cells to dead when the clear action is triggered.
#### Scenario: Clear grid
- **WHEN** the user triggers the clear action
- **THEN** all cells become dead
### Requirement: Grid lines can be toggled
The system SHALL show or hide grid lines when the toggle_grid action is triggered.
#### Scenario: Toggle lines
- **WHEN** the user triggers the toggle_grid action
- **THEN** grid line visibility flips
+44
View File
@@ -0,0 +1,44 @@
# display-scaling Specification
## Purpose
Keeps the application rendering crisp on displays with integer or fractional scaling by converting between logical and physical coordinates.
## Requirements
### Requirement: Fractional scale is applied when available
The system SHALL use the compositor-provided fractional preferred scale for rendering when the fractional scale protocol is available.
#### Scenario: Fractional display
- **WHEN** the compositor sends a preferred scale of 1.5
- **THEN** the render buffer is created at 1.5 times the logical size
- **AND** the buffer is displayed at the logical size
### Requirement: Integer scale is used as fallback
The system SHALL use the output's integer scale when fractional scale is unavailable.
#### Scenario: No fractional protocol
- **WHEN** the compositor does not support fractional scaling
- **THEN** the render buffer uses the output integer scale
- **AND** the buffer is displayed at the logical size
### Requirement: Input coordinates remain logical
The system SHALL report mouse positions and grid cells in logical coordinates regardless of the active scale.
#### Scenario: Scaled click
- **WHEN** the user clicks on a scaled display
- **THEN** the selected grid cell matches the visual location of the cursor
### Requirement: Rendering uses physical resolution
The system SHALL render all content at physical resolution so that lines and cells stay sharp at the active scale.
#### Scenario: Sharp cells
- **WHEN** the application runs on a 2x scaled display
- **THEN** cells are drawn with 2 physical pixels per logical pixel
### Requirement: Scale changes trigger re-render
The system SHALL recreate buffers and re-render when the active scale changes.
#### Scenario: Scale change
- **WHEN** the compositor changes the preferred scale
- **THEN** the application updates its buffers
- **AND** subsequent frames use the new scale
+59
View File
@@ -0,0 +1,59 @@
# keyboard-bindings Specification
## Purpose
Allows users to configure keyboard shortcuts that map key combinations to application actions, with sensible built-in defaults when no configuration file exists.
## Requirements
### Requirement: Default bindings are available without configuration
The system SHALL provide built-in default bindings for all documented actions, including digits 1-4 selecting the Glider, Blinker, Block, and Toad patterns, Space rotating, Escape cancelling, and Ctrl+Q quitting.
#### Scenario: No config file present
- **WHEN** the application starts and `~/.config/wlgol/keys.conf` does not exist
- **THEN** the application uses the built-in default bindings
- **AND** creates the config file with the default contents
### Requirement: Configuration file overrides defaults by merge
The system SHALL read `~/.config/wlgol/keys.conf` as TOML when it exists and merge its entries over the built-in defaults, so only specified keys change.
#### Scenario: Partial override
- **WHEN** the config file defines a binding for key "1" only
- **THEN** key "1" uses the configured action
- **AND** all other keys keep their default bindings
#### Scenario: Full replacement of a key
- **WHEN** the config file defines a binding for a key that has a default
- **THEN** the configured action replaces the default for that key
### Requirement: Key combinations are modifier-aware
The system SHALL match a binding only when the modifiers required by the binding are active, supporting Ctrl, Shift, Alt, and Super.
#### Scenario: Modifier required
- **WHEN** the user presses Q while Ctrl is held and a `ctrl+q = quit` binding exists
- **THEN** the quit action is triggered
#### Scenario: Modifier not held
- **WHEN** the user presses Q without Ctrl while a `ctrl+q = quit` binding exists
- **THEN** the quit action is not triggered by that binding
### Requirement: Readable key aliases are supported
The system SHALL accept human-readable key names such as space, escape, enter, and arrow names in binding keys.
#### Scenario: Alias binding
- **WHEN** the config file contains `space = rotate`
- **THEN** pressing the space bar triggers the rotate action
### Requirement: Malformed entries are ignored
The system SHALL ignore malformed or unrecognized binding entries and continue using defaults for the affected keys.
#### Scenario: Invalid entry
- **WHEN** the config file contains an entry with an unknown action
- **THEN** that entry is ignored
- **AND** the application still starts successfully
### Requirement: Actions trigger on key press
The system SHALL dispatch a bound action when the corresponding key is pressed, not on release.
#### Scenario: Press triggers action
- **WHEN** the user presses a key with a valid binding
- **THEN** the bound action is invoked once for that press
+60
View File
@@ -0,0 +1,60 @@
# pattern-placement Specification
## Purpose
Lets users select a Game of Life pattern, preview it under the mouse cursor, rotate it, and place copies on the grid with a click.
## Requirements
### Requirement: Selecting a pattern arms a placement tool
The system SHALL activate a placement tool for the selected pattern when a `select:<pattern>` action is triggered, and show a preview following the cursor.
#### Scenario: Tool activation
- **WHEN** the user triggers `select:glider`
- **THEN** a glider preview appears at the current cursor position
- **AND** the placement tool remains active
### Requirement: Preview follows cursor motion
The system SHALL update the preview position to track the current mouse cursor cell while a tool is active.
#### Scenario: Mouse moves
- **WHEN** the user moves the mouse while a tool is active
- **THEN** the preview moves to the cell under the cursor
### Requirement: Tool can be rotated in 90-degree steps
The system SHALL rotate the active preview by 90 degrees each time the rotate action is triggered.
#### Scenario: Rotate preview
- **WHEN** the user triggers the rotate action while a tool is active
- **THEN** the preview rotates 90 degrees clockwise
- **AND** remains at the same anchor cell
### Requirement: Left click places the pattern when valid
The system SHALL write the active pattern's alive cells into the grid at the preview position when the user left-clicks and the pattern fits entirely within the grid.
#### Scenario: Valid placement
- **WHEN** the user left-clicks while a tool is active and the preview fits in the grid
- **THEN** the pattern's cells become alive in the grid
- **AND** the tool remains active for further placement
### Requirement: Invalid placement is rejected
The system SHALL not modify the grid when a left click occurs while the preview extends outside the grid.
#### Scenario: Out of bounds
- **WHEN** the user left-clicks while the preview extends outside the grid
- **THEN** no cells are changed
- **AND** the tool remains active
### Requirement: Escape cancels the active tool
The system SHALL deactivate the placement tool and remove the preview when the cancel action is triggered.
#### Scenario: Cancel placement
- **WHEN** the user triggers the cancel action while a tool is active
- **THEN** the preview disappears
- **AND** no further clicks place a pattern until a tool is selected again
### Requirement: Click without active tool does nothing
The system SHALL ignore left clicks when no placement tool is active.
#### Scenario: No tool
- **WHEN** the user left-clicks while no tool is active
- **THEN** the grid is unchanged
+68
View File
@@ -0,0 +1,68 @@
#include "pattern.h"
#include <limits.h>
#include <stddef.h>
static const cell_offset_t glider_cells[] = {
{1,0},{2,1},{0,2},{1,2},{2,2}
};
static const cell_offset_t blinker_cells[] = {
{0,0},{1,0},{2,0}
};
static const cell_offset_t block_cells[] = {
{0,0},{1,0},{0,1},{1,1}
};
static const cell_offset_t toad_cells[] = {
{1,0},{2,0},{3,0},{0,1},{1,1},{2,1}
};
static const pattern_t patterns[] = {
{"glider", 5, glider_cells},
{"blinker", 3, blinker_cells},
{"block", 4, block_cells},
{"toad", 6, toad_cells},
};
int pattern_count(void) { return 4; }
const pattern_t *pattern_get(int index) {
if (index < 0 || index >= pattern_count()) return NULL;
return &patterns[index];
}
int pattern_rotated_offsets(int index, int rotation, int *out_dx, int *out_dy) {
const pattern_t *p = pattern_get(index);
if (!p) return 0;
rotation = ((rotation % 4) + 4) % 4;
int minx = INT_MAX, miny = INT_MAX;
int tx[16], ty[16];
for (int i = 0; i < p->count; i++) {
int x = p->cells[i].dx;
int y = p->cells[i].dy;
for (int r = 0; r < rotation; r++) {
int nx = y;
int ny = -x;
x = nx; y = ny;
}
tx[i] = x; ty[i] = y;
if (x < minx) minx = x;
if (y < miny) miny = y;
}
for (int i = 0; i < p->count; i++) {
out_dx[i] = tx[i] - minx;
out_dy[i] = ty[i] - miny;
}
return p->count;
}
void pattern_bounds(int index, int rotation, int *w, int *h) {
int dx[16], dy[16];
int n = pattern_rotated_offsets(index, rotation, dx, dy);
int maxx = 0, maxy = 0;
for (int i = 0; i < n; i++) {
if (dx[i] > maxx) maxx = dx[i];
if (dy[i] > maxy) maxy = dy[i];
}
if (w) *w = maxx + 1;
if (h) *h = maxy + 1;
}
+49
View File
@@ -0,0 +1,49 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xkbcommon/xkbcommon.h>
#include "config.h"
int main(void) {
uint32_t mods;
char *key = NULL;
assert(config_parse_mods("ctrl+q", &mods, &key) == 0);
assert(mods == MOD_CTRL);
assert(strcmp(key, "q") == 0);
free(key);
assert(config_parse_mods("space", &mods, &key) == 0);
assert(mods == 0);
assert(strcmp(key, "space") == 0);
free(key);
assert(config_parse_action("quit") == ACTION_QUIT);
assert(config_parse_action("select:glider") == ACTION_SELECT_GLIDER);
assert(config_parse_action("nope") == ACTION_NONE);
config_t cfg = config_defaults();
xkb_keysym_t one = xkb_keysym_from_name("1", XKB_KEYSYM_CASE_INSENSITIVE);
xkb_keysym_t space = xkb_keysym_from_name("space", XKB_KEYSYM_CASE_INSENSITIVE);
assert(config_lookup(&cfg, 0, one) == ACTION_SELECT_GLIDER);
assert(config_lookup(&cfg, 0, space) == ACTION_ROTATE);
const char *path = "/tmp/opencode/test_keys.conf";
FILE *fp = fopen(path, "w");
assert(fp);
fputs("[bindings]\n\"1\" = \"quit\"\n", fp);
fclose(fp);
config_t cfg2 = config_load(path);
assert(config_lookup(&cfg2, 0, one) == ACTION_QUIT);
assert(config_lookup(&cfg2, 0, space) == ACTION_ROTATE);
config_free(&cfg);
config_free(&cfg2);
remove(path);
printf("config tests passed\n");
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include <assert.h>
#include <stdio.h>
#include "grid.h"
int main(void) {
grid_t g;
grid_init(&g, 4, 4);
assert(grid_get(&g, 0, 0) == 0);
grid_set(&g, 1, 2, 1);
assert(grid_get(&g, 1, 2) == 1);
assert(grid_get(&g, -1, 0) == 0);
assert(grid_get(&g, 4, 4) == 0);
grid_set(&g, 0, 0, 1);
grid_resize(&g, 6, 2);
assert(grid_get(&g, 0, 0) == 1);
assert(grid_get(&g, 1, 2) == 0);
grid_clear(&g);
assert(grid_get(&g, 0, 0) == 0);
grid_free(&g);
printf("grid tests passed\n");
return 0;
}
+28
View File
@@ -0,0 +1,28 @@
#include <assert.h>
#include <stdio.h>
#include "pattern.h"
int main(void) {
assert(pattern_count() == 4);
const void *p = pattern_get(0);
(void)p;
int dx[16], dy[16];
int n = pattern_rotated_offsets(0, 0, dx, dy);
assert(n == 5);
int w, h;
pattern_bounds(0, 1, &w, &h);
assert(w == 3 && h == 3);
int dx2[16], dy2[16];
pattern_rotated_offsets(0, 1, dx2, dy2);
int same = 1;
for (int i = 0; i < n; i++) {
if (dx[i] != dx2[i] || dy[i] != dy2[i]) same = 0;
}
assert(!same);
printf("pattern tests passed\n");
return 0;
}
+19 -1
View File
@@ -8,6 +8,7 @@
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <tkeyboard.h> #include <tkeyboard.h>
#include <config.h>
static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size)
{ {
@@ -61,9 +62,26 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t seri
printf("keyboard_key: scancode=%d state=%d keysym=%u\n", key, state, sym); printf("keyboard_key: scancode=%d state=%d keysym=%u\n", key, state, sym);
uint32_t mods = 0;
if (kbd->xkb_state != NULL && kbd->xkb_keymap != NULL)
{
xkb_mod_index_t ci = xkb_keymap_mod_get_index(kbd->xkb_keymap, "Control");
if (ci != XKB_MOD_INVALID && xkb_state_mod_index_is_active(kbd->xkb_state, ci, XKB_STATE_MODS_EFFECTIVE))
mods |= MOD_CTRL;
xkb_mod_index_t si = xkb_keymap_mod_get_index(kbd->xkb_keymap, "Shift");
if (si != XKB_MOD_INVALID && xkb_state_mod_index_is_active(kbd->xkb_state, si, XKB_STATE_MODS_EFFECTIVE))
mods |= MOD_SHIFT;
xkb_mod_index_t ai = xkb_keymap_mod_get_index(kbd->xkb_keymap, "Mod1");
if (ai != XKB_MOD_INVALID && xkb_state_mod_index_is_active(kbd->xkb_state, ai, XKB_STATE_MODS_EFFECTIVE))
mods |= MOD_ALT;
xkb_mod_index_t sui = xkb_keymap_mod_get_index(kbd->xkb_keymap, "Mod4");
if (sui != XKB_MOD_INVALID && xkb_state_mod_index_is_active(kbd->xkb_state, sui, XKB_STATE_MODS_EFFECTIVE))
mods |= MOD_SUPER;
}
if (kbd->handler != NULL) if (kbd->handler != NULL)
{ {
kbd->handler(key, state, sym, kbd->user_data); kbd->handler(key, state, sym, mods, kbd->user_data);
} }
} }
+2392
View File
File diff suppressed because it is too large Load Diff
+17 -2
View File
@@ -21,12 +21,17 @@ static void pointer_leave_handler(void *data, struct wl_pointer *pointer, uint32
static void pointer_motion_handler(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) static void pointer_motion_handler(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
{ {
// not implemented struct pointer_data *pd = data;
pd->x = wl_fixed_to_double(sx);
pd->y = wl_fixed_to_double(sy);
} }
static void pointer_button_handler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) static void pointer_button_handler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{ {
// not implemented struct pointer_data *pd = data;
if (pd->button_handler) {
pd->button_handler(button, state, pd->x, pd->y, pd->user_data);
}
} }
static void pointer_axis_handler(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) static void pointer_axis_handler(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value)
@@ -66,8 +71,18 @@ const struct wl_pointer_listener pointer_listener = {
.axis_stop = pointer_axis_stop_handler, .axis_stop = pointer_axis_stop_handler,
}; };
void pointer_set_button_handler(struct pointer_data *pointer_data, pointer_button_handler_t handler, void *user_data)
{
pointer_data->button_handler = handler;
pointer_data->user_data = user_data;
}
void pointer_init_cursor(struct pointer_data *pointer_data, struct wl_compositor *compositor, struct wl_shm *shm) void pointer_init_cursor(struct pointer_data *pointer_data, struct wl_compositor *compositor, struct wl_shm *shm)
{ {
pointer_data->x = 0;
pointer_data->y = 0;
pointer_data->button_handler = NULL;
pointer_data->user_data = NULL;
pointer_data->cursor_theme = pointer_data->cursor_theme =
wl_cursor_theme_load(NULL, 24, shm); wl_cursor_theme_load(NULL, 24, shm);
pointer_data->cursor = wl_cursor_theme_get_cursor(pointer_data->cursor_theme, "left_ptr"); pointer_data->cursor = wl_cursor_theme_get_cursor(pointer_data->cursor_theme, "left_ptr");
+177
View File
@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="viewporter">
<copyright>
Copyright © 2013-2016 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="wp_viewporter" version="1">
<description summary="surface cropping and scaling">
The global interface exposing surface cropping and scaling
capabilities is used to instantiate an interface extension for a
wl_surface object. This extended interface will then allow
cropping and scaling the surface contents, effectively
disconnecting the direct relationship between the buffer and the
surface size.
</description>
<request name="destroy" type="destructor">
<description summary="unbind from the cropping and scaling interface">
Informs the server that the client will not be using this
protocol object anymore. This does not affect any other objects,
wp_viewport objects included.
</description>
</request>
<enum name="error">
<entry name="viewport_exists" value="0"
summary="the surface already has a viewport object associated"/>
</enum>
<request name="get_viewport">
<description summary="extend surface interface for crop and scale">
Instantiate an interface extension for the given wl_surface to
crop and scale its content. If the given wl_surface already has
a wp_viewport object associated, the viewport_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_viewport"
summary="the new viewport interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_viewport" version="1">
<description summary="crop and scale interface to a wl_surface">
An additional interface to a wl_surface object, which allows the
client to specify the cropping and scaling of the surface
contents.
This interface works with two concepts: the source rectangle (src_x,
src_y, src_width, src_height), and the destination size (dst_width,
dst_height). The contents of the source rectangle are scaled to the
destination size, and content outside the source rectangle is ignored.
This state is double-buffered, see wl_surface.commit.
The two parts of crop and scale state are independent: the source
rectangle, and the destination size. Initially both are unset, that
is, no scaling is applied. The whole of the current wl_buffer is
used as the source, and the surface size is as defined in
wl_surface.attach.
If the destination size is set, it causes the surface size to become
dst_width, dst_height. The source (rectangle) is scaled to exactly
this size. This overrides whatever the attached wl_buffer size is,
unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
has no content and therefore no size. Otherwise, the size is always
at least 1x1 in surface local coordinates.
If the source rectangle is set, it defines what area of the wl_buffer is
taken as the source. If the source rectangle is set and the destination
size is not set, then src_width and src_height must be integers, and the
surface size becomes the source rectangle size. This results in cropping
without scaling. If src_width or src_height are not integers and
destination size is not set, the bad_size protocol error is raised when
the surface state is applied.
The coordinate transformations from buffer pixel coordinates up to
the surface-local coordinates happen in the following order:
1. buffer_transform (wl_surface.set_buffer_transform)
2. buffer_scale (wl_surface.set_buffer_scale)
3. crop and scale (wp_viewport.set*)
This means, that the source rectangle coordinates of crop and scale
are given in the coordinates after the buffer transform and scale,
i.e. in the coordinates that would be the surface-local coordinates
if the crop and scale was not applied.
If src_x or src_y are negative, the bad_value protocol error is raised.
Otherwise, if the source rectangle is partially or completely outside of
the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
when the surface state is applied. A NULL wl_buffer does not raise the
out_of_buffer error.
If the wl_surface associated with the wp_viewport is destroyed,
all wp_viewport requests except 'destroy' raise the protocol error
no_surface.
If the wp_viewport object is destroyed, the crop and scale
state is removed from the wl_surface. The change will be applied
on the next wl_surface.commit.
</description>
<request name="destroy" type="destructor">
<description summary="remove scaling and cropping from the surface">
The associated wl_surface's crop and scale state is removed.
The change is applied on the next wl_surface.commit.
</description>
</request>
<enum name="error">
<entry name="bad_value" value="0"
summary="negative or zero values in width or height"/>
<entry name="bad_size" value="1"
summary="destination size is not integer"/>
<entry name="out_of_buffer" value="2"
summary="source rectangle extends outside of the content area"/>
<entry name="no_surface" value="3"
summary="the wl_surface was destroyed"/>
</enum>
<request name="set_source">
<description summary="set the source rectangle for cropping">
Set the source rectangle of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If all of x, y, width and height are -1.0, the source rectangle is
unset instead. Any other set of values where width or height are zero
or negative, or x or y are negative, raise the bad_value protocol
error.
The crop and scale state is double-buffered, see wl_surface.commit.
</description>
<arg name="x" type="fixed" summary="source rectangle x"/>
<arg name="y" type="fixed" summary="source rectangle y"/>
<arg name="width" type="fixed" summary="source rectangle width"/>
<arg name="height" type="fixed" summary="source rectangle height"/>
</request>
<request name="set_destination">
<description summary="set the surface size for scaling">
Set the destination size of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If width is -1 and height is -1, the destination size is unset
instead. Any other pair of values for width and height that
contains zero or negative values raises the bad_value protocol
error.
The crop and scale state is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="int" summary="surface width"/>
<arg name="height" type="int" summary="surface height"/>
</request>
</interface>
</protocol>
+297 -71
View File
@@ -1,46 +1,109 @@
#include <bits/time.h> #include <bits/time.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <math.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <syscall.h> #include <syscall.h>
#include <unistd.h> #include <unistd.h>
#include <linux/input-event-codes.h>
#include <xdg-shell.h> #include <xdg-shell.h>
#include <viewporter.h>
#include <fractional-scale-v1.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h> #include <xkbcommon/xkbcommon-keysyms.h>
#include <tkeyboard.h> #include <tkeyboard.h>
#include <tpointer.h> #include <tpointer.h>
#include <config.h>
#include <grid.h>
#include <pattern.h>
struct wl_compositor *compositor; struct wl_compositor *compositor;
struct wl_shm *shm; struct wl_shm *shm;
struct xdg_wm_base *xdg_shell; struct xdg_wm_base *xdg_shell;
struct wl_seat *wl_seat; struct wl_seat *wl_seat;
struct wl_output *wl_output = NULL;
int output_scale_factor = 1;
struct keyboard_data keyboard_data; struct keyboard_data keyboard_data;
struct pointer_data pointer_data; struct pointer_data pointer_data;
int running = 1; int running = 1;
int released = 0; 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;
double cursor_x = 0, cursor_y = 0;
int click_pending = 0;
double click_x = 0, click_y = 0;
int show_grid_lines = 1;
int sim_paused = 1;
struct timespec mytime; struct timespec mytime;
struct canvas struct canvas
{ {
int width; int width; /* physical */
int height; int height; /* physical */
int stride; int stride;
int size; int size;
struct wl_buffer *buffer; struct wl_buffer *buffer;
unsigned char *data; unsigned char *data;
struct wl_shm_pool *pool;
int fd;
struct wl_surface *surface; struct wl_surface *surface;
struct wl_callback *frame; struct wl_callback *frame;
} canvas; } 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)width;(void)height;(void)refresh; }
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 // registry
void registry_global_handler( void registry_global_handler(
void *data, struct wl_registry *registry, void *data, struct wl_registry *registry,
@@ -68,6 +131,25 @@ void registry_global_handler(
wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5); wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5);
printf("Got wl_seat (%d)\n", version); 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 registry_global_remove_handler(
@@ -89,6 +171,9 @@ void xdg_toplevel_configure_handler(
struct wl_array *states) struct wl_array *states)
{ {
printf("xdg_toplevel_configure: %dx%d\n", width, height); 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) void xdg_toplevel_close_handler(void *data, struct xdg_toplevel *xdg_toplevel)
@@ -144,16 +229,66 @@ const struct wl_buffer_listener wl_buffer_listener = {
void redraw(); // forward declaration void redraw(); // forward declaration
static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, void *user_data) static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t mods, void *user_data)
{ {
(void)key; (void)key;
(void)user_data; (void)user_data;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED && sym == XKB_KEY_Escape) if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return;
{
printf("Escape pressed, exiting.\n"); action_t a = config_lookup(&g_config, mods, sym);
printf("action=%d\n", a);
switch (a) {
case ACTION_QUIT:
running = 0; 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_CLEAR:
printf("clear requested\n");
break;
case ACTION_PAUSE:
sim_paused = !sim_paused;
break;
case ACTION_STEP:
printf("step requested\n");
break;
case ACTION_SPEED_UP:
case ACTION_SPEED_DOWN:
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 (state != WL_POINTER_BUTTON_STATE_PRESSED) return;
if (button != BTN_LEFT) return;
click_x = x;
click_y = y;
click_pending = 1;
printf("click at %.1f %.1f\n", x, y);
} }
void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time) void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time)
@@ -173,6 +308,44 @@ void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time)
const struct wl_callback_listener wl_surface_frame_listener = { const struct wl_callback_listener wl_surface_frame_listener = {
.done = wl_callack_done}; .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);
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) int main(void)
{ {
clock_gettime(CLOCK_MONOTONIC, &mytime); clock_gettime(CLOCK_MONOTONIC, &mytime);
@@ -188,6 +361,14 @@ int main(void)
canvas.surface = wl_compositor_create_surface(compositor); 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_surface *xdg_surface = xdg_wm_base_get_xdg_surface(xdg_shell, canvas.surface);
struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
@@ -205,35 +386,29 @@ int main(void)
wl_keyboard_add_listener(keyboard_data.keyboard, &keyboard_listener, &keyboard_data); wl_keyboard_add_listener(keyboard_data.keyboard, &keyboard_listener, &keyboard_data);
pointer_init_cursor(&pointer_data, compositor, shm); 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); pointer_data.pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(pointer_data.pointer, &pointer_listener, &pointer_data); wl_pointer_add_listener(pointer_data.pointer, &pointer_listener, &pointer_data);
canvas.width = 400; canvas.fd = -1;
canvas.height = 400; grid_init(&grid, logical_width / 8, logical_height / 8);
canvas.stride = canvas.width * 4; grid.show_lines = 1;
canvas.size = canvas.stride * canvas.height; // bytes
// open an anonymous file and write some zero bytes to it
int fd = syscall(SYS_memfd_create, "buffer", 0);
ftruncate(fd, canvas.size);
// map it to the memory
canvas.data =
mmap(NULL, canvas.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// turn it into a shared memory pool
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, canvas.size);
close(fd); // mapped, so can be closed
// allocate the buffer in that pool
canvas.buffer = wl_shm_pool_create_buffer(
pool, 0, canvas.width, canvas.height, canvas.stride, WL_SHM_FORMAT_ARGB8888);
wl_buffer_add_listener(canvas.buffer, &wl_buffer_listener, NULL);
// wait for the surface to be configured // wait for the surface to be configured
wl_display_roundtrip(display); wl_display_roundtrip(display);
/* 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(); redraw();
while (wl_display_dispatch(display) != -1 && running) while (wl_display_dispatch(display) != -1 && running)
@@ -244,7 +419,8 @@ int main(void)
xdg_toplevel_destroy(xdg_toplevel); xdg_toplevel_destroy(xdg_toplevel);
xdg_surface_destroy(xdg_surface); xdg_surface_destroy(xdg_surface);
wl_surface_destroy(canvas.surface); wl_surface_destroy(canvas.surface);
wl_shm_pool_destroy(pool); if (canvas.pool) wl_shm_pool_destroy(canvas.pool);
if (canvas.buffer) wl_buffer_destroy(canvas.buffer);
wl_keyboard_destroy(keyboard_data.keyboard); wl_keyboard_destroy(keyboard_data.keyboard);
if (keyboard_data.xkb_state != NULL) if (keyboard_data.xkb_state != NULL)
{ {
@@ -266,59 +442,109 @@ unsigned char mycolour = 0;
char direction = 1; char direction = 1;
int framecounter = 0; int framecounter = 0;
void redraw() 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;
clock_t start = clock(); for (int y = y0; y < y0 + h && y < canvas.height; y++) {
if (y < 0) continue;
mycolour += direction; for (int x = x0; x < x0 + w && x < canvas.width; x++) {
switch (mycolour) if (x < 0) continue;
{ struct pixel {
case 0: // lower limit, start to increase
direction = 1;
break;
case 255: // upper limit, start to decrease
direction = -1;
break;
}
// printf("dred=%d\n", dred);
// draw into buffer
for (int x = 0; x < canvas.width; x++)
{
for (int y = 0; y < canvas.height; y++)
{
struct pixel
{
// little-endian ARGB
unsigned char blue; unsigned char blue;
unsigned char green; unsigned char green;
unsigned char red; unsigned char red;
unsigned char alpha; unsigned char alpha;
} *px = (struct pixel *)(canvas.data + y * canvas.stride + x * 4); } *px = (struct pixel *)(canvas.data + y * canvas.stride + x * 4);
px->red = r;
px->green = g;
px->blue = b;
px->alpha = a;
}
}
}
// draw a stripes pattern void redraw()
if ((x + y) % 30 < 10) {
{ if (need_resize) {
// transparent canvas_resize(logical_width, logical_height, current_scale);
px->alpha = 0;
} }
else if ((x + y) % 30 < 20)
{ clock_t start = clock();
// yellow
px->alpha = 255; /* update cursor from pointer */
px->red = 255; cursor_x = pointer_data.x;
px->green = 255; cursor_y = pointer_data.y;
px->blue = 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; }
} }
else if (ok) {
{ for (int i = 0; i < n; i++) {
// semitransparent red grid_set(&grid, cx + dx[i], cy + dy[i], 1);
px->alpha = 128;
px->red = mycolour;
px->green = 0;
px->blue = (255 - mycolour);
} }
} }
} }
}
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 // callback
canvas.frame = wl_surface_frame(canvas.surface); canvas.frame = wl_surface_frame(canvas.surface);
wl_callback_add_listener(canvas.frame, &wl_surface_frame_listener, NULL); wl_callback_add_listener(canvas.frame, &wl_surface_frame_listener, NULL);