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:
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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 */
|
||||||
@@ -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
|
||||||
|
|||||||
+19
-19
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -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
@@ -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>
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user