Compare commits
2 Commits
67f0c169f5
...
eb376e51b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
eb376e51b7
|
|||
|
3e09dec613
|
33
src/config/defaults.c
Normal file
33
src/config/defaults.c
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "config/defaults.h"
|
||||
|
||||
#include "base/macros.h"
|
||||
|
||||
bool config_defaults_build(const zdwm_api_t *api,
|
||||
zdwm_config_builder_t *builder,
|
||||
const zdwm_output_info_t *outputs,
|
||||
size_t output_count) {
|
||||
if (!api || !builder || !outputs || output_count == 0) return false;
|
||||
|
||||
zdwm_layout_id_t tile_id = api->register_layout(
|
||||
builder, "tile", "[]=", "builtin tile", api->builtin_layouts.tile);
|
||||
zdwm_layout_id_t monocle_id = api->register_layout(
|
||||
builder, "monocle", "[M]", "builtin monocle", api->builtin_layouts.monocle);
|
||||
zdwm_layout_id_t floating_id =
|
||||
api->register_layout(builder, "floating", "><>", "floating", nullptr);
|
||||
if (tile_id == ZDWM_LAYOUT_ID_INVALID ||
|
||||
monocle_id == ZDWM_LAYOUT_ID_INVALID ||
|
||||
floating_id == ZDWM_LAYOUT_ID_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
zdwm_layout_id_t layout_ids[] = {tile_id, monocle_id, floating_id};
|
||||
for (size_t i = 0; i < output_count; i++) {
|
||||
if (api->define_workspace(builder, i, "main", layout_ids,
|
||||
countof(layout_ids),
|
||||
tile_id) == ZDWM_WORKSPACE_ID_INVALID) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
5
src/config/defaults.h
Normal file
5
src/config/defaults.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <zdwm/config.h>
|
||||
|
||||
zdwm_config_setup_fn config_defaults_build;
|
||||
@@ -55,26 +55,22 @@ bool config_loader_load(config_loader_t *loader, const char *override_path) {
|
||||
if (!loader) return false;
|
||||
config_loader_cleanup(loader);
|
||||
|
||||
char *path = nullptr;
|
||||
bool is_explicit = false;
|
||||
if (!config_loader_resolve_path(&path, &is_explicit, override_path)) {
|
||||
if (!config_loader_resolve_path(&loader->path, &is_explicit, override_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loader->path = path;
|
||||
if (access(loader->path, F_OK) != 0) {
|
||||
if (!is_explicit) return true;
|
||||
|
||||
if (access(path, F_OK) != 0) {
|
||||
if (is_explicit) {
|
||||
warn("config library not found: %s", path);
|
||||
warn("config library not found: %s", loader->path);
|
||||
config_loader_cleanup(loader);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
loader->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||
loader->handle = dlopen(loader->path, RTLD_NOW | RTLD_LOCAL);
|
||||
if (!loader->handle) {
|
||||
warn("failed to load config library %s: %s", path, dlerror());
|
||||
warn("failed to load config library %s: %s", loader->path, dlerror());
|
||||
config_loader_cleanup(loader);
|
||||
return false;
|
||||
}
|
||||
@@ -82,13 +78,11 @@ bool config_loader_load(config_loader_t *loader, const char *override_path) {
|
||||
dlerror();
|
||||
loader->setup = (zdwm_config_setup_fn *)dlsym(loader->handle, "setup");
|
||||
const char *error = dlerror();
|
||||
if (error) {
|
||||
warn("failed to resolve setup from %s: %s", path, error);
|
||||
if (!error) return true;
|
||||
|
||||
warn("failed to resolve setup from %s: %s", loader->path, error);
|
||||
config_loader_cleanup(loader);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void config_loader_cleanup(config_loader_t *loader) {
|
||||
|
||||
178
src/config/runtime_config.c
Normal file
178
src/config/runtime_config.c
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "config/runtime_config.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <zdwm/config.h>
|
||||
|
||||
#include "base/memory.h"
|
||||
#include "config/defaults.h"
|
||||
#include "config/loader.h"
|
||||
#include "core/layout.h"
|
||||
#include "core/runtime.h"
|
||||
#include "core/wm_desc.h"
|
||||
|
||||
struct zdwm_config_builder_t {
|
||||
layout_registry_t layouts;
|
||||
workspace_desc_t *workspaces;
|
||||
size_t workspace_count;
|
||||
size_t workspace_capacity;
|
||||
size_t output_count;
|
||||
};
|
||||
|
||||
void runtime_config_cleanup(runtime_init_desc_t *desc) {
|
||||
if (!desc) return;
|
||||
|
||||
if (desc->layouts.slots || desc->layouts.slot_count ||
|
||||
desc->layouts.slot_capacity) {
|
||||
layout_registry_cleanup(&desc->layouts);
|
||||
}
|
||||
workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count);
|
||||
if (desc->config_module_handle) dlclose(desc->config_module_handle);
|
||||
desc->config_module_handle = nullptr;
|
||||
}
|
||||
|
||||
static void config_builder_cleanup(zdwm_config_builder_t *builder) {
|
||||
if (!builder) return;
|
||||
|
||||
if (builder->layouts.slots || builder->layouts.slot_count ||
|
||||
builder->layouts.slot_capacity) {
|
||||
layout_registry_cleanup(&builder->layouts);
|
||||
}
|
||||
workspace_desc_list_cleanup(&builder->workspaces, &builder->workspace_count);
|
||||
builder->workspace_capacity = 0;
|
||||
builder->output_count = 0;
|
||||
}
|
||||
|
||||
static bool config_builder_init(zdwm_config_builder_t *builder,
|
||||
size_t output_count) {
|
||||
if (!builder || output_count == 0) return false;
|
||||
|
||||
p_clear(builder, 1);
|
||||
builder->output_count = output_count;
|
||||
layout_registry_init(&builder->layouts);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool config_builder_ensure_workspace_capacity(
|
||||
zdwm_config_builder_t *builder) {
|
||||
if (!builder) return false;
|
||||
if (builder->workspace_count < builder->workspace_capacity) return true;
|
||||
|
||||
size_t capacity = next_capacity(builder->workspace_capacity);
|
||||
if (!builder->workspaces) {
|
||||
builder->workspaces = p_new(workspace_desc_t, capacity);
|
||||
} else {
|
||||
p_realloc(&builder->workspaces, capacity);
|
||||
}
|
||||
builder->workspace_capacity = capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
static layout_id_t runtime_config_register_layout(
|
||||
zdwm_config_builder_t *builder, const char *name, const char *symbol,
|
||||
const char *description, layout_fn fn) {
|
||||
if (!builder || !name || !symbol) return ZDWM_LAYOUT_ID_INVALID;
|
||||
|
||||
return layout_register(&builder->layouts, name, symbol, description, fn);
|
||||
}
|
||||
|
||||
static workspace_id_t runtime_config_define_workspace(
|
||||
zdwm_config_builder_t *builder, size_t output_index, const char *name,
|
||||
const layout_id_t *layout_ids, size_t layout_count,
|
||||
layout_id_t initial_layout_id) {
|
||||
if (!builder) return ZDWM_WORKSPACE_ID_INVALID;
|
||||
if (output_index >= builder->output_count) return ZDWM_WORKSPACE_ID_INVALID;
|
||||
if (builder->workspace_count >= (size_t)ZDWM_WORKSPACE_ID_INVALID) {
|
||||
return ZDWM_WORKSPACE_ID_INVALID;
|
||||
}
|
||||
|
||||
workspace_desc_t desc = {
|
||||
.output_index = output_index,
|
||||
.name = name,
|
||||
.layout_ids = layout_ids,
|
||||
.layout_count = layout_count,
|
||||
.initial_layout_id = initial_layout_id,
|
||||
};
|
||||
if (!workspace_desc_valid(&desc, builder->output_count)) {
|
||||
return ZDWM_WORKSPACE_ID_INVALID;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < layout_count; i++) {
|
||||
if (!layout_slot_get(&builder->layouts, layout_ids[i])) {
|
||||
return ZDWM_WORKSPACE_ID_INVALID;
|
||||
}
|
||||
}
|
||||
if (!config_builder_ensure_workspace_capacity(builder)) {
|
||||
return ZDWM_WORKSPACE_ID_INVALID;
|
||||
}
|
||||
|
||||
workspace_id_t workspace_id = (workspace_id_t)builder->workspace_count;
|
||||
workspace_desc_t *slot = &builder->workspaces[builder->workspace_count];
|
||||
slot->output_index = output_index;
|
||||
slot->name = p_strdup(name);
|
||||
slot->layout_ids = p_copy(layout_ids, layout_count);
|
||||
slot->layout_count = layout_count;
|
||||
slot->initial_layout_id = initial_layout_id;
|
||||
builder->workspace_count++;
|
||||
return workspace_id;
|
||||
}
|
||||
|
||||
static bool config_builder_finish(zdwm_config_builder_t *builder,
|
||||
runtime_init_desc_t *out) {
|
||||
if (!builder || !out) return false;
|
||||
|
||||
if (!builder->layouts.slot_count || !builder->workspace_count) return false;
|
||||
|
||||
if (!layout_registry_move(&builder->layouts, &out->layouts)) return false;
|
||||
out->workspaces = builder->workspaces;
|
||||
out->workspace_count = builder->workspace_count;
|
||||
builder->workspaces = nullptr;
|
||||
builder->workspace_count = 0;
|
||||
builder->workspace_capacity = 0;
|
||||
builder->output_count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool runtime_config_build(zdwm_config_setup_fn *setup,
|
||||
const output_info_t *outputs,
|
||||
size_t output_count,
|
||||
runtime_init_desc_t *out) {
|
||||
if (!setup || !out) return false;
|
||||
zdwm_config_builder_t builder = {0};
|
||||
if (!config_builder_init(&builder, output_count)) return false;
|
||||
|
||||
zdwm_api_t api = {
|
||||
.abi_version = ZDWM_CONFIG_ABI_VERSION,
|
||||
.builtin_layouts =
|
||||
{
|
||||
.tile = nullptr,
|
||||
.monocle = nullptr,
|
||||
},
|
||||
.register_layout = runtime_config_register_layout,
|
||||
.define_workspace = runtime_config_define_workspace,
|
||||
};
|
||||
bool ok = setup(&api, &builder, outputs, output_count) &&
|
||||
config_builder_finish(&builder, out);
|
||||
config_builder_cleanup(&builder);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool runtime_config_load(const char *override_path, runtime_init_desc_t *out) {
|
||||
if (!out || !out->outputs || out->output_count == 0) return false;
|
||||
runtime_config_cleanup(out);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
bool ok = false;
|
||||
if (!config_loader_load(&loader, override_path)) goto cleanup;
|
||||
|
||||
zdwm_config_setup_fn *setup =
|
||||
loader.setup ? loader.setup : config_defaults_build;
|
||||
ok = runtime_config_build(setup, out->outputs, out->output_count, out);
|
||||
if (ok) {
|
||||
out->config_module_handle = loader.handle;
|
||||
loader.handle = nullptr;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
config_loader_cleanup(&loader);
|
||||
return ok;
|
||||
}
|
||||
6
src/config/runtime_config.h
Normal file
6
src/config/runtime_config.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct runtime_init_desc_t runtime_init_desc_t;
|
||||
|
||||
bool runtime_config_load(const char *override_path, runtime_init_desc_t *out);
|
||||
void runtime_config_cleanup(runtime_init_desc_t *desc);
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "core/runtime.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "base/memory.h"
|
||||
#include "core/backend.h"
|
||||
@@ -36,35 +36,22 @@ static bool runtime_init_desc_valid(const runtime_init_desc_t *desc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void runtime_workspace_desc_list_cleanup(workspace_desc_t **list,
|
||||
size_t *count) {
|
||||
if (!list || !count) return;
|
||||
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
workspace_desc_t *workspace = &(*list)[i];
|
||||
p_delete(&workspace->name);
|
||||
p_delete(&workspace->layout_ids);
|
||||
}
|
||||
|
||||
p_delete(list);
|
||||
*count = 0;
|
||||
}
|
||||
|
||||
bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc) {
|
||||
if (!runtime || !runtime_init_desc_valid(desc)) return false;
|
||||
|
||||
p_clear(runtime, 1);
|
||||
runtime->backend = desc->backend;
|
||||
runtime->layouts = desc->layouts;
|
||||
runtime->config_module_handle = desc->config_module_handle;
|
||||
|
||||
desc->backend = nullptr;
|
||||
p_clear(&desc->layouts, 1);
|
||||
desc->config_module_handle = nullptr;
|
||||
|
||||
state_init(&runtime->state, desc->outputs, desc->output_count,
|
||||
desc->workspaces, desc->workspace_count);
|
||||
|
||||
runtime_workspace_desc_list_cleanup(&desc->workspaces,
|
||||
&desc->workspace_count);
|
||||
workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count);
|
||||
desc->outputs = nullptr;
|
||||
desc->output_count = 0;
|
||||
|
||||
@@ -84,10 +71,11 @@ void runtime_init_desc_cleanup(runtime_init_desc_t *desc) {
|
||||
layout_registry_cleanup(&desc->layouts);
|
||||
}
|
||||
|
||||
runtime_workspace_desc_list_cleanup(&desc->workspaces,
|
||||
&desc->workspace_count);
|
||||
workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count);
|
||||
desc->outputs = nullptr;
|
||||
desc->output_count = 0;
|
||||
if (desc->config_module_handle) dlclose(desc->config_module_handle);
|
||||
desc->config_module_handle = nullptr;
|
||||
}
|
||||
|
||||
void runtime_shutdown(runtime_t *runtime) {
|
||||
@@ -99,4 +87,6 @@ void runtime_shutdown(runtime_t *runtime) {
|
||||
state_cleanup(&runtime->state);
|
||||
backend_destroy(runtime->backend);
|
||||
runtime->backend = nullptr;
|
||||
if (runtime->config_module_handle) dlclose(runtime->config_module_handle);
|
||||
runtime->config_module_handle = nullptr;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/backend.h"
|
||||
#include "core/layout.h"
|
||||
#include "core/state.h"
|
||||
@@ -12,6 +14,7 @@ typedef struct runtime_init_desc_t {
|
||||
layout_registry_t layouts;
|
||||
workspace_desc_t *workspaces;
|
||||
size_t workspace_count;
|
||||
void *config_module_handle;
|
||||
} runtime_init_desc_t;
|
||||
|
||||
typedef struct runtime_t {
|
||||
@@ -21,6 +24,7 @@ typedef struct runtime_t {
|
||||
state_t state;
|
||||
layout_registry_t layouts;
|
||||
backend_t *backend;
|
||||
void *config_module_handle;
|
||||
} runtime_t;
|
||||
|
||||
bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "base/memory.h"
|
||||
#include "core/types.h"
|
||||
|
||||
/*
|
||||
@@ -32,6 +33,28 @@ typedef struct workspace_desc_t {
|
||||
layout_id_t initial_layout_id;
|
||||
} workspace_desc_t;
|
||||
|
||||
static inline void workspace_desc_cleanup(workspace_desc_t *workspace) {
|
||||
if (!workspace) return;
|
||||
|
||||
p_delete(&workspace->name);
|
||||
p_delete(&workspace->layout_ids);
|
||||
workspace->output_index = 0;
|
||||
workspace->layout_count = 0;
|
||||
workspace->initial_layout_id = ZDWM_LAYOUT_ID_INVALID;
|
||||
}
|
||||
|
||||
static inline void workspace_desc_list_cleanup(workspace_desc_t **list,
|
||||
size_t *count) {
|
||||
if (!list || !count) return;
|
||||
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
workspace_desc_cleanup(&(*list)[i]);
|
||||
}
|
||||
|
||||
p_delete(list);
|
||||
*count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* workspace 描述校验接口。
|
||||
*
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
add_subdirectory(backend)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(core)
|
||||
|
||||
74
tests/config/CMakeLists.txt
Normal file
74
tests/config/CMakeLists.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
set(CONFIG_FIXTURE_TARGET "zdwm-config-fixture")
|
||||
set(CONFIG_FIXTURE_NO_SETUP_TARGET "zdwm-config-fixture-no-setup")
|
||||
set(CONFIG_LOADER_TEST_APP_NAME "zdwm-config-loader-tests")
|
||||
set(RUNTIME_CONFIG_TEST_APP_NAME "zdwm-runtime-config-tests")
|
||||
|
||||
add_library(${CONFIG_FIXTURE_TARGET} SHARED
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fixture_valid_config.c
|
||||
)
|
||||
target_include_directories(${CONFIG_FIXTURE_TARGET}
|
||||
PRIVATE ${INCLUDE_DIR}
|
||||
)
|
||||
|
||||
add_library(${CONFIG_FIXTURE_NO_SETUP_TARGET} SHARED
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fixture_without_setup.c
|
||||
)
|
||||
|
||||
add_executable(${CONFIG_LOADER_TEST_APP_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/config_loader_test.c
|
||||
${SOURCE_DIR}/base/log.c
|
||||
${SOURCE_DIR}/config/loader.c
|
||||
)
|
||||
target_include_directories(${CONFIG_LOADER_TEST_APP_NAME} SYSTEM
|
||||
PRIVATE ${INCLUDE_DIR}
|
||||
)
|
||||
target_include_directories(${CONFIG_LOADER_TEST_APP_NAME}
|
||||
PRIVATE ${SOURCE_DIR}
|
||||
PRIVATE ${BUILD_DIR}
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
target_link_libraries(${CONFIG_LOADER_TEST_APP_NAME}
|
||||
PRIVATE ${CMAKE_DL_LIBS}
|
||||
)
|
||||
add_dependencies(${CONFIG_LOADER_TEST_APP_NAME}
|
||||
${CONFIG_FIXTURE_TARGET}
|
||||
${CONFIG_FIXTURE_NO_SETUP_TARGET}
|
||||
)
|
||||
|
||||
add_executable(${RUNTIME_CONFIG_TEST_APP_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/runtime_config_test.c
|
||||
${SOURCE_DIR}/base/log.c
|
||||
${SOURCE_DIR}/config/defaults.c
|
||||
${SOURCE_DIR}/config/loader.c
|
||||
${SOURCE_DIR}/config/runtime_config.c
|
||||
${SOURCE_DIR}/core/layout.c
|
||||
${SOURCE_DIR}/core/runtime.c
|
||||
${SOURCE_DIR}/core/state.c
|
||||
)
|
||||
target_include_directories(${RUNTIME_CONFIG_TEST_APP_NAME} SYSTEM
|
||||
PRIVATE ${INCLUDE_DIR}
|
||||
)
|
||||
target_include_directories(${RUNTIME_CONFIG_TEST_APP_NAME}
|
||||
PRIVATE ${SOURCE_DIR}
|
||||
PRIVATE ${BUILD_DIR}
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
target_link_libraries(${RUNTIME_CONFIG_TEST_APP_NAME}
|
||||
PRIVATE ${CMAKE_DL_LIBS}
|
||||
)
|
||||
add_dependencies(${RUNTIME_CONFIG_TEST_APP_NAME}
|
||||
${CONFIG_FIXTURE_TARGET}
|
||||
${CONFIG_FIXTURE_NO_SETUP_TARGET}
|
||||
)
|
||||
|
||||
add_test(NAME ${CONFIG_LOADER_TEST_APP_NAME}
|
||||
COMMAND $<TARGET_FILE:${CONFIG_LOADER_TEST_APP_NAME}>
|
||||
$<TARGET_FILE:${CONFIG_FIXTURE_TARGET}>
|
||||
$<TARGET_FILE:${CONFIG_FIXTURE_NO_SETUP_TARGET}>
|
||||
)
|
||||
|
||||
add_test(NAME ${RUNTIME_CONFIG_TEST_APP_NAME}
|
||||
COMMAND $<TARGET_FILE:${RUNTIME_CONFIG_TEST_APP_NAME}>
|
||||
$<TARGET_FILE:${CONFIG_FIXTURE_TARGET}>
|
||||
$<TARGET_FILE:${CONFIG_FIXTURE_NO_SETUP_TARGET}>
|
||||
)
|
||||
173
tests/config/config_loader_test.c
Normal file
173
tests/config/config_loader_test.c
Normal file
@@ -0,0 +1,173 @@
|
||||
#include "config/loader.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
static void test_config_loader_loads_explicit_override(const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, fixture_path));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, fixture_path) == 0);
|
||||
assert(loader.handle);
|
||||
assert(loader.setup);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
}
|
||||
|
||||
static void test_config_loader_override_has_priority_over_env(
|
||||
const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
assert(setenv("ZDWM_CONFIG_LIB", "/tmp/definitely-missing-config.so", 1) == 0);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, fixture_path));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, fixture_path) == 0);
|
||||
assert(loader.handle);
|
||||
assert(loader.setup);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
}
|
||||
|
||||
static void test_config_loader_uses_zdwm_config_lib_env(
|
||||
const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
assert(setenv("ZDWM_CONFIG_LIB", fixture_path, 1) == 0);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, nullptr));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, fixture_path) == 0);
|
||||
assert(loader.handle);
|
||||
assert(loader.setup);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
}
|
||||
|
||||
static void test_config_loader_uses_xdg_config_home(const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
char temp_root[PATH_MAX] = {0};
|
||||
char config_dir[PATH_MAX] = {0};
|
||||
char config_path[PATH_MAX] = {0};
|
||||
|
||||
config_test_make_temp_dir(temp_root, sizeof(temp_root),
|
||||
"zdwm-config-loader-xdg");
|
||||
config_test_join_path(config_dir, sizeof(config_dir), temp_root, "zdwm");
|
||||
config_test_join_path(config_path, sizeof(config_path), config_dir,
|
||||
"config.so");
|
||||
config_test_mkdir(config_dir);
|
||||
config_test_symlink(fixture_path, config_path);
|
||||
assert(setenv("XDG_CONFIG_HOME", temp_root, 1) == 0);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, nullptr));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, config_path) == 0);
|
||||
assert(loader.handle);
|
||||
assert(loader.setup);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
config_test_unlink(config_path);
|
||||
config_test_rmdir(config_dir);
|
||||
config_test_rmdir(temp_root);
|
||||
}
|
||||
|
||||
static void test_config_loader_uses_home_fallback(const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
char temp_root[PATH_MAX] = {0};
|
||||
char config_root[PATH_MAX] = {0};
|
||||
char zdwm_dir[PATH_MAX] = {0};
|
||||
char config_path[PATH_MAX] = {0};
|
||||
|
||||
config_test_make_temp_dir(temp_root, sizeof(temp_root),
|
||||
"zdwm-config-loader-home");
|
||||
config_test_join_path(config_root, sizeof(config_root), temp_root, ".config");
|
||||
config_test_join_path(zdwm_dir, sizeof(zdwm_dir), config_root, "zdwm");
|
||||
config_test_join_path(config_path, sizeof(config_path), zdwm_dir,
|
||||
"config.so");
|
||||
config_test_mkdir(config_root);
|
||||
config_test_mkdir(zdwm_dir);
|
||||
config_test_symlink(fixture_path, config_path);
|
||||
assert(setenv("HOME", temp_root, 1) == 0);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, nullptr));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, config_path) == 0);
|
||||
assert(loader.handle);
|
||||
assert(loader.setup);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
config_test_unlink(config_path);
|
||||
config_test_rmdir(zdwm_dir);
|
||||
config_test_rmdir(config_root);
|
||||
config_test_rmdir(temp_root);
|
||||
}
|
||||
|
||||
static void test_config_loader_allows_missing_implicit_library(void) {
|
||||
config_test_clear_env();
|
||||
|
||||
char temp_root[PATH_MAX] = {0};
|
||||
char expected_path[PATH_MAX] = {0};
|
||||
|
||||
config_test_make_temp_dir(temp_root, sizeof(temp_root),
|
||||
"zdwm-config-loader-missing");
|
||||
config_test_join_path(expected_path, sizeof(expected_path), temp_root,
|
||||
"zdwm/config.so");
|
||||
assert(setenv("XDG_CONFIG_HOME", temp_root, 1) == 0);
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(config_loader_load(&loader, nullptr));
|
||||
assert(loader.path);
|
||||
assert(strcmp(loader.path, expected_path) == 0);
|
||||
assert(loader.handle == nullptr);
|
||||
assert(loader.setup == nullptr);
|
||||
|
||||
config_loader_cleanup(&loader);
|
||||
config_test_rmdir(temp_root);
|
||||
}
|
||||
|
||||
static void test_config_loader_rejects_missing_explicit_library(void) {
|
||||
config_test_clear_env();
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(!config_loader_load(&loader, "/tmp/definitely-missing-config.so"));
|
||||
assert(loader.path == nullptr);
|
||||
assert(loader.handle == nullptr);
|
||||
assert(loader.setup == nullptr);
|
||||
}
|
||||
|
||||
static void test_config_loader_rejects_library_without_setup(
|
||||
const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
config_loader_t loader = {0};
|
||||
assert(!config_loader_load(&loader, fixture_path));
|
||||
assert(loader.path == nullptr);
|
||||
assert(loader.handle == nullptr);
|
||||
assert(loader.setup == nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 3);
|
||||
|
||||
test_config_loader_loads_explicit_override(argv[1]);
|
||||
test_config_loader_override_has_priority_over_env(argv[1]);
|
||||
test_config_loader_uses_zdwm_config_lib_env(argv[1]);
|
||||
test_config_loader_uses_xdg_config_home(argv[1]);
|
||||
test_config_loader_uses_home_fallback(argv[1]);
|
||||
test_config_loader_allows_missing_implicit_library();
|
||||
test_config_loader_rejects_missing_explicit_library();
|
||||
test_config_loader_rejects_library_without_setup(argv[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
tests/config/fixture_valid_config.c
Normal file
28
tests/config/fixture_valid_config.c
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <zdwm/config.h>
|
||||
|
||||
static bool fixture_layout(const zdwm_layout_ctx_t *ctx,
|
||||
zdwm_layout_result_t *out) {
|
||||
if (!ctx || !out) return false;
|
||||
|
||||
zdwm_layout_result_reset(out);
|
||||
if (!ctx->window_ids || ctx->window_count == 0) return true;
|
||||
|
||||
zdwm_layout_result_push(out, (zdwm_layout_item_t){
|
||||
.window_id = ctx->window_ids[0],
|
||||
.rect = ctx->workarea,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setup(const zdwm_api_t *api, zdwm_config_builder_t *builder,
|
||||
const zdwm_output_info_t *outputs, size_t output_count) {
|
||||
if (!api || !builder || !outputs || output_count == 0) return false;
|
||||
|
||||
zdwm_layout_id_t layout_id =
|
||||
api->register_layout(builder, "test-only", "[T]", "fixture layout",
|
||||
fixture_layout);
|
||||
if (layout_id == ZDWM_LAYOUT_ID_INVALID) return false;
|
||||
|
||||
return api->define_workspace(builder, 0, "from-so", &layout_id, 1,
|
||||
layout_id) != ZDWM_WORKSPACE_ID_INVALID;
|
||||
}
|
||||
1
tests/config/fixture_without_setup.c
Normal file
1
tests/config/fixture_without_setup.c
Normal file
@@ -0,0 +1 @@
|
||||
int zdwm_config_fixture_without_setup_symbol(void) { return 1; }
|
||||
48
tests/config/helpers.h
Normal file
48
tests/config/helpers.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static inline void config_test_clear_env(void) {
|
||||
assert(unsetenv("ZDWM_CONFIG_LIB") == 0);
|
||||
assert(unsetenv("XDG_CONFIG_HOME") == 0);
|
||||
assert(unsetenv("HOME") == 0);
|
||||
}
|
||||
|
||||
static inline void config_test_make_temp_dir(char *path, size_t path_size,
|
||||
const char *prefix) {
|
||||
int len = snprintf(path, path_size, "/tmp/%s-XXXXXX", prefix);
|
||||
assert(len > 0);
|
||||
assert((size_t)len < path_size);
|
||||
assert(mkdtemp(path) == path);
|
||||
}
|
||||
|
||||
static inline void config_test_join_path(char *out, size_t out_size,
|
||||
const char *base,
|
||||
const char *suffix) {
|
||||
int len = snprintf(out, out_size, "%s/%s", base, suffix);
|
||||
assert(len > 0);
|
||||
assert((size_t)len < out_size);
|
||||
}
|
||||
|
||||
static inline void config_test_mkdir(const char *path) {
|
||||
assert(mkdir(path, 0700) == 0);
|
||||
}
|
||||
|
||||
static inline void config_test_symlink(const char *target, const char *path) {
|
||||
assert(symlink(target, path) == 0);
|
||||
}
|
||||
|
||||
static inline void config_test_unlink(const char *path) {
|
||||
assert(unlink(path) == 0);
|
||||
}
|
||||
|
||||
static inline void config_test_rmdir(const char *path) {
|
||||
assert(rmdir(path) == 0);
|
||||
}
|
||||
213
tests/config/runtime_config_test.c
Normal file
213
tests/config/runtime_config_test.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "config/runtime_config.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "core/layout.h"
|
||||
#include "core/runtime.h"
|
||||
#include "helpers.h"
|
||||
|
||||
struct backend_t {
|
||||
int unused;
|
||||
};
|
||||
|
||||
void backend_destroy(backend_t *backend) { free(backend); }
|
||||
|
||||
static backend_t *test_backend_create(void) {
|
||||
return calloc(1, sizeof(backend_t));
|
||||
}
|
||||
|
||||
static void assert_default_layouts(const runtime_init_desc_t *desc) {
|
||||
assert(desc);
|
||||
assert(layout_registry_count(&desc->layouts) == 3);
|
||||
|
||||
const layout_slot_t *tile = layout_registry_at(&desc->layouts, 0);
|
||||
const layout_slot_t *monocle = layout_registry_at(&desc->layouts, 1);
|
||||
const layout_slot_t *floating = layout_registry_at(&desc->layouts, 2);
|
||||
|
||||
assert(tile);
|
||||
assert(monocle);
|
||||
assert(floating);
|
||||
assert(strcmp(tile->name, "tile") == 0);
|
||||
assert(strcmp(monocle->name, "monocle") == 0);
|
||||
assert(strcmp(floating->name, "floating") == 0);
|
||||
}
|
||||
|
||||
static void test_runtime_config_loads_user_library(const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
output_info_t outputs[] = {
|
||||
{
|
||||
.name = "HDMI-A-1",
|
||||
.geometry = {.x = 0, .y = 0, .width = 1920, .height = 1080},
|
||||
},
|
||||
};
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.outputs = outputs,
|
||||
.output_count = 1,
|
||||
};
|
||||
assert(runtime_config_load(fixture_path, &desc));
|
||||
assert(desc.config_module_handle != nullptr);
|
||||
assert(layout_registry_count(&desc.layouts) == 1);
|
||||
assert(desc.workspace_count == 1);
|
||||
assert(strcmp(desc.workspaces[0].name, "from-so") == 0);
|
||||
assert(desc.workspaces[0].output_index == 0);
|
||||
assert(desc.workspaces[0].layout_count == 1);
|
||||
assert(desc.workspaces[0].initial_layout_id == 0);
|
||||
|
||||
const layout_slot_t *layout = layout_registry_at(&desc.layouts, 0);
|
||||
assert(layout);
|
||||
assert(strcmp(layout->name, "test-only") == 0);
|
||||
assert(strcmp(layout->symbol, "[T]") == 0);
|
||||
|
||||
runtime_config_cleanup(&desc);
|
||||
}
|
||||
|
||||
static void test_runtime_config_keeps_module_loaded_after_runtime_init(
|
||||
const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
output_info_t outputs[] = {
|
||||
{
|
||||
.name = "HDMI-A-1",
|
||||
.geometry = {.x = 0, .y = 0, .width = 1920, .height = 1080},
|
||||
},
|
||||
};
|
||||
window_id_t window_ids[] = {42};
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.backend = test_backend_create(),
|
||||
.outputs = outputs,
|
||||
.output_count = 1,
|
||||
};
|
||||
assert(desc.backend);
|
||||
assert(runtime_config_load(fixture_path, &desc));
|
||||
|
||||
runtime_t runtime = {0};
|
||||
assert(runtime_init(&runtime, &desc));
|
||||
assert(desc.config_module_handle == nullptr);
|
||||
runtime_init_desc_cleanup(&desc);
|
||||
|
||||
assert(runtime.config_module_handle != nullptr);
|
||||
|
||||
layout_fn layout = layout_get(&runtime.layouts, 0);
|
||||
assert(layout != nullptr);
|
||||
|
||||
layout_result_t result = {0};
|
||||
layout_result_init(&result);
|
||||
|
||||
layout_ctx_t ctx = {
|
||||
.workspace_id = 0,
|
||||
.focused_window_id = window_ids[0],
|
||||
.workarea = {.x = 11, .y = 22, .width = 333, .height = 444},
|
||||
.window_ids = window_ids,
|
||||
.window_count = 1,
|
||||
};
|
||||
assert(layout(&ctx, &result));
|
||||
assert(result.item_count == 1);
|
||||
assert(result.items[0].window_id == window_ids[0]);
|
||||
assert(result.items[0].rect.x == ctx.workarea.x);
|
||||
assert(result.items[0].rect.y == ctx.workarea.y);
|
||||
assert(result.items[0].rect.width == ctx.workarea.width);
|
||||
assert(result.items[0].rect.height == ctx.workarea.height);
|
||||
|
||||
layout_result_cleanup(&result);
|
||||
runtime_shutdown(&runtime);
|
||||
assert(runtime.config_module_handle == nullptr);
|
||||
}
|
||||
|
||||
static void test_runtime_config_falls_back_to_defaults_when_implicit_library_is_missing(
|
||||
void) {
|
||||
config_test_clear_env();
|
||||
|
||||
output_info_t outputs[] = {
|
||||
{
|
||||
.name = "eDP-1",
|
||||
.geometry = {.x = 0, .y = 0, .width = 1920, .height = 1080},
|
||||
},
|
||||
{
|
||||
.name = "DP-1",
|
||||
.geometry = {.x = 1920, .y = 0, .width = 2560, .height = 1440},
|
||||
},
|
||||
};
|
||||
|
||||
char temp_root[PATH_MAX] = {0};
|
||||
config_test_make_temp_dir(temp_root, sizeof(temp_root),
|
||||
"zdwm-runtime-config-missing");
|
||||
assert(setenv("XDG_CONFIG_HOME", temp_root, 1) == 0);
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.outputs = outputs,
|
||||
.output_count = 2,
|
||||
};
|
||||
assert(runtime_config_load(nullptr, &desc));
|
||||
assert(desc.config_module_handle == nullptr);
|
||||
assert_default_layouts(&desc);
|
||||
assert(desc.workspace_count == 2);
|
||||
assert(strcmp(desc.workspaces[0].name, "main") == 0);
|
||||
assert(strcmp(desc.workspaces[1].name, "main") == 0);
|
||||
assert(desc.workspaces[0].output_index == 0);
|
||||
assert(desc.workspaces[1].output_index == 1);
|
||||
|
||||
runtime_config_cleanup(&desc);
|
||||
config_test_rmdir(temp_root);
|
||||
}
|
||||
|
||||
static void test_runtime_config_rejects_missing_explicit_library(void) {
|
||||
config_test_clear_env();
|
||||
|
||||
output_info_t outputs[] = {
|
||||
{
|
||||
.name = "HDMI-A-1",
|
||||
.geometry = {.x = 0, .y = 0, .width = 1920, .height = 1080},
|
||||
},
|
||||
};
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.outputs = outputs,
|
||||
.output_count = 1,
|
||||
};
|
||||
assert(!runtime_config_load("/tmp/definitely-missing-config.so", &desc));
|
||||
assert(layout_registry_count(&desc.layouts) == 0);
|
||||
assert(desc.workspace_count == 0);
|
||||
assert(desc.workspaces == nullptr);
|
||||
assert(desc.config_module_handle == nullptr);
|
||||
}
|
||||
|
||||
static void test_runtime_config_rejects_library_without_setup(
|
||||
const char *fixture_path) {
|
||||
config_test_clear_env();
|
||||
|
||||
output_info_t outputs[] = {
|
||||
{
|
||||
.name = "HDMI-A-1",
|
||||
.geometry = {.x = 0, .y = 0, .width = 1920, .height = 1080},
|
||||
},
|
||||
};
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.outputs = outputs,
|
||||
.output_count = 1,
|
||||
};
|
||||
assert(!runtime_config_load(fixture_path, &desc));
|
||||
assert(layout_registry_count(&desc.layouts) == 0);
|
||||
assert(desc.workspace_count == 0);
|
||||
assert(desc.workspaces == nullptr);
|
||||
assert(desc.config_module_handle == nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 3);
|
||||
|
||||
test_runtime_config_loads_user_library(argv[1]);
|
||||
test_runtime_config_keeps_module_loaded_after_runtime_init(argv[1]);
|
||||
test_runtime_config_falls_back_to_defaults_when_implicit_library_is_missing();
|
||||
test_runtime_config_rejects_missing_explicit_library();
|
||||
test_runtime_config_rejects_library_without_setup(argv[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -4,6 +4,9 @@ add_executable(${TEST_APP_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main.c
|
||||
${SOURCE_DIR}/base/log.c
|
||||
${SOURCE_DIR}/backend/output_utils.c
|
||||
${SOURCE_DIR}/config/defaults.c
|
||||
${SOURCE_DIR}/config/loader.c
|
||||
${SOURCE_DIR}/config/runtime_config.c
|
||||
${SOURCE_DIR}/backend/x11/backend.c
|
||||
${SOURCE_DIR}/core/layout.c
|
||||
${SOURCE_DIR}/core/runtime.c
|
||||
@@ -21,6 +24,7 @@ target_include_directories(${TEST_APP_NAME}
|
||||
|
||||
target_link_libraries(${TEST_APP_NAME}
|
||||
PRIVATE m
|
||||
PRIVATE ${CMAKE_DL_LIBS}
|
||||
PRIVATE ${deps_LIBRARIES}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,41 @@
|
||||
#include "base/log.h"
|
||||
#include "config/runtime_config.h"
|
||||
#include "core/backend.h"
|
||||
#include "core/runtime.h"
|
||||
|
||||
static void bootstrap(runtime_t *runtime) {
|
||||
backend_t *backend = backend_create(nullptr);
|
||||
backend_detect_t *detect = backend_detect(backend);
|
||||
|
||||
if (!backend || !detect || detect->output_count == 0) {
|
||||
fatal("backend detect failed");
|
||||
}
|
||||
|
||||
runtime_init_desc_t desc = {
|
||||
.backend = backend,
|
||||
.outputs = detect->outputs,
|
||||
.output_count = detect->output_count,
|
||||
};
|
||||
if (!runtime_config_load(nullptr, &desc)) {
|
||||
fatal("failed to build runtime inputs");
|
||||
}
|
||||
|
||||
backend = nullptr;
|
||||
|
||||
bool inited = runtime_init(runtime, &desc);
|
||||
|
||||
runtime_init_desc_cleanup(&desc);
|
||||
backend_detect_destroy(detect);
|
||||
|
||||
if (!inited) {
|
||||
fatal("zdwm runtime init failed");
|
||||
}
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
runtime_t runtime = {0};
|
||||
runtime_init(&runtime, nullptr);
|
||||
|
||||
bootstrap(&runtime);
|
||||
runtime_shutdown(&runtime);
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user