Files
zdwm/src/runtime/runtime.c

493 lines
14 KiB
C

#include "runtime/runtime.h"
#include <dlfcn.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/layout.h>
#include "bar/bar.h"
#include "base/array.h"
#include "base/color.h"
#include "base/log.h"
#include "base/memory.h"
#include "base/window_list.h"
#include "config/runtime_config.h"
#include "core/backend.h"
#include "core/binding.h"
#include "core/command_buffer.h"
#include "core/event.h"
#include "core/layout.h"
#include "core/listeners.h"
#include "core/plan.h"
#include "core/policy.h"
#include "core/rules.h"
#include "core/state.h"
#include "core/types.h"
#include "core/window.h"
#include "core/wm_desc.h"
typedef struct runtime_t {
bool running;
bool will_restart;
plan_t plan;
command_buffer_t command_buffer;
state_t state;
layout_result_t layout_result;
layout_registry_t layouts;
rules_t rules;
border_config_t border;
backend_t *backend;
void *config_module_handle;
binding_table_t *binding_table;
listeners_t listeners;
bar_t bar;
window_list_t bar_windows;
} runtime_t;
static bool runtime_workspace_desc_has_valid_layouts(
const layout_registry_t *layouts,
const workspace_desc_t *workspace
) {
if (!layouts || !workspace) return false;
for (size_t i = 0; i < workspace->layout_count; i++) {
if (!layout_slot_get(layouts, workspace->layout_ids[i])) return false;
}
return true;
}
static bool runtime_init_desc_valid(const runtime_init_desc_t *desc) {
if (!desc || !desc->backend || !desc->outputs || desc->output_count == 0) {
return false;
}
if (!desc->layouts.slots || desc->layouts.slot_count == 0) return false;
if (!desc->workspaces || desc->workspace_count == 0) return false;
for (size_t i = 0; i < desc->workspace_count; i++) {
const workspace_desc_t *workspace = &desc->workspaces[i];
if (!workspace_desc_valid(workspace, desc->output_count)) return false;
if (!runtime_workspace_desc_has_valid_layouts(&desc->layouts, workspace)) {
return false;
}
}
return true;
}
static 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;
layout_registry_move(&desc->layouts, &runtime->layouts);
rules_move(&desc->rules, &runtime->rules);
runtime->border = desc->border;
runtime->config_module_handle = desc->config_module_handle;
runtime->binding_table = desc->binding_table;
runtime->listeners = desc->listeners;
runtime->bar.config = desc->bar;
desc->backend = nullptr;
desc->config_module_handle = nullptr;
desc->binding_table = nullptr;
p_clear(&desc->listeners, 1);
p_clear(&desc->bar, 1);
state_init(
&runtime->state,
desc->outputs,
desc->output_count,
desc->workspaces,
desc->workspace_count
);
workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count);
desc->outputs = nullptr;
desc->output_count = 0;
return true;
}
static void runtime_init_desc_cleanup(runtime_init_desc_t *desc) {
if (!desc) return;
binding_table_destroy(desc->binding_table);
desc->binding_table = nullptr;
listeners_cleanup(&desc->listeners);
if (desc->backend) {
backend_destroy(desc->backend);
desc->backend = nullptr;
}
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);
desc->outputs = nullptr;
desc->output_count = 0;
if (desc->config_module_handle) dlclose(desc->config_module_handle);
desc->config_module_handle = nullptr;
}
static void runtime_shutdown(runtime_t *runtime) {
runtime->running = false;
plan_cleanup(&runtime->plan);
command_buffer_cleanup(&runtime->command_buffer);
layout_registry_cleanup(&runtime->layouts);
rules_cleanup(&runtime->rules);
state_cleanup(&runtime->state);
layout_result_cleanup(&runtime->layout_result);
binding_table_destroy(runtime->binding_table);
runtime->binding_table = nullptr;
listeners_cleanup(&runtime->listeners);
window_list_cleanup(&runtime->bar_windows);
backend_destroy(runtime->backend);
runtime->backend = nullptr;
if (runtime->config_module_handle) dlclose(runtime->config_module_handle);
runtime->config_module_handle = nullptr;
bar_cleanup(&runtime->bar);
}
static void runtime_notify_initial_state(runtime_t *runtime) {
auto listeners = &runtime->listeners;
auto state = &runtime->state;
auto output = state_output_at(state, state->current_output_index);
if (output) listeners_notify_current_output(listeners, output->id);
listeners_notify_initial_workspaces(listeners, state);
for (size_t i = 0; i < state_output_count(state); ++i) {
auto item = state_output_at(state, i);
if (!item) continue;
listeners_notify_workspace_active(
listeners,
item->id,
item->current_workspace_id
);
}
for (size_t i = 0; i < state_workspace_count(state); ++i) {
auto workspace = state_workspace_at(state, i);
if (!workspace) continue;
listeners_notify_layout(
listeners,
&runtime->layouts,
workspace->id,
workspace->layout_id
);
}
listeners_notify_binding_mode(listeners, runtime->binding_table);
}
static void runtime_init_bar(runtime_t *runtime) {
auto bar_height = runtime->bar.config.height;
if (bar_height <= 0) return;
auto show_top = runtime->bar.config.show_top;
inset_t inset = {
.top = show_top ? bar_height : 0,
.bottom = show_top ? 0 : bar_height,
};
auto state = &runtime->state;
auto count = state_output_count(state);
auto bar = &runtime->bar;
bar->bars = p_new(bar_output_t, count);
bar->count = count;
bar->visible = true;
color_parse(bar->config.bg, &bar->palette.bg);
auto backend = runtime->backend;
for (size_t i = 0; i < count; ++i) {
auto output = state_output_at(state, i);
state_output_inset_workarea(state, output->id, inset);
auto geometry = output->geometry;
rect_t bar_rect = {
.x = geometry.x,
.y = show_top ? 0 : geometry.y + geometry.height - bar_height,
.width = geometry.width,
.height = bar_height,
};
auto color = bar->palette.bg.argb;
auto bar_window = backend_create_bar_window(backend, bar_rect, color);
auto bar_output = &bar->bars[i];
bar_output->cr = bar_window.cr;
bar_output->window_id = bar_window.window_id;
bar_output->output_id = output->id;
}
bar_init(&runtime->bar);
}
static void runtime_setup(runtime_t *runtime) {
size_t bind_count = 0;
auto bindings =
binding_table_get_current_bindings(runtime->binding_table, &bind_count);
if (!bindings) return;
auto backend = runtime->backend;
auto plan = &runtime->plan;
plan_reset(plan);
auto keys = p_new(key_bind_t, bind_count);
for (size_t i = 0; i < bind_count; ++i) {
keys[i].keysym = bindings[i].keysym;
keys[i].modifiers = bindings[i].modifiers;
}
effect_t effect_bind_key = {
.type = ZDWM_EFFECT_BIND_KEY,
.as.bind_key = {.count = bind_count, .keys = keys},
};
plan_push_effect(plan, &effect_bind_key);
backend_apply_effect(backend, plan->effects, plan->count);
plan_reset(plan);
runtime_notify_initial_state(runtime);
runtime_init_bar(runtime);
}
static const layout_result_t *runtime_layout_calc(runtime_t *runtime) {
auto result = &runtime->layout_result;
zdwm_layout_result_reset(result);
auto state = &runtime->state;
for (size_t i = 0; i < state->output_count; ++i) {
auto output = state_output_at(state, i);
if (!output) continue;
auto workspace = state_workspace_get(state, output->current_workspace_id);
if (!workspace) continue;
auto layout_slot = layout_slot_get(&runtime->layouts, workspace->layout_id);
if (!layout_slot || !layout_slot->fn) continue;
size_t window_count = 0;
size_t window_list_capacity = 0;
window_id_t *window_ids = nullptr;
for (size_t j = 0; j < state_window_count(state); j++) {
auto window = state_window_at(state, j);
if (!window || window->workspace_id != workspace->id) continue;
if (window_need_layout(window)) {
window_id_t *window_id_slot =
array_push(window_ids, window_count, window_list_capacity);
*window_id_slot = window->id;
} else if (window->fullscreen) {
layout_item_t item = {
.window_id = window->id,
.rect = output->geometry,
};
layout_result_push(result, item);
} else if (window->maximized) {
layout_item_t item = {
.window_id = window->id,
.rect = output->workarea,
};
layout_result_push(result, item);
}
}
if (window_count) {
zdwm_layout_ctx_t ctx = {
.workspace_id = workspace->id,
.focused_window_id = workspace->focused_window_id,
.output_geometry = output->geometry,
.workarea = output->workarea,
.window_ids = window_ids,
.window_count = window_count,
};
layout_slot->fn(&ctx, result);
}
if (window_ids) p_delete(&window_ids);
}
return result->item_count ? result : nullptr;
}
static void runtime_apply_window_rect(
state_t *state,
window_id_t window_id,
rect_t rect,
plan_t *plan
) {
auto window = state_window_get(state, window_id);
if (!window) return;
auto need_move = window_need_move(window, rect.x, rect.y);
auto need_resize = window_need_resize(window, rect.width, rect.height);
uint32_t changed_fields = 0u;
if (need_move) {
changed_fields |= ZDWM_CONFIGURE_FIELD_X | ZDWM_CONFIGURE_FIELD_Y;
}
if (need_resize) {
changed_fields |= ZDWM_CONFIGURE_FIELD_WIDTH | ZDWM_CONFIGURE_FIELD_HEIGHT;
changed_fields |= ZDWM_CONFIGURE_FIELD_BORDER_WIDTH;
}
effect_t configure_effect = {
.type = ZDWM_EFFECT_CONFIGURE_WINDOW,
.as.configure = {
.window = window_id,
.x = rect.x,
.y = rect.y,
.width = rect.width - 2 * (int32_t)window->border_width,
.height = rect.height - 2 * (int32_t)window->border_width,
.border_width = window->border_width,
.changed_fields = changed_fields,
}
};
plan_push_effect(plan, &configure_effect);
if (need_move || need_resize) {
state_window_set_frame_rect(state, window_id, rect);
}
}
static void runtime_arrange(runtime_t *runtime) {
auto result = runtime_layout_calc(runtime);
if (!result) return;
auto state = &runtime->state;
auto plan = &runtime->plan;
for (size_t i = 0; i < result->item_count; ++i) {
auto item = &result->items[i];
runtime_apply_window_rect(state, item->window_id, item->rect, plan);
}
}
static policy_context_t policy_context_init(runtime_t *runtime) {
policy_context_t ctx = {
.bind_table = runtime->binding_table,
.state = &runtime->state,
.rules = &runtime->rules,
.listeners = &runtime->listeners,
.border = &runtime->border,
.layouts = &runtime->layouts,
.bar = {
.visible = &runtime->bar.visible,
.windows = &runtime->bar_windows,
.height = runtime->bar.config.height,
.show_top = runtime->bar.config.show_top,
},
};
return ctx;
}
static void runtime_scan(runtime_t *runtime) {
auto result = backend_scan_windows(runtime->backend);
if (!result) return;
policy_context_t ctx = policy_context_init(runtime);
ctx.listeners = nullptr;
auto backend = runtime->backend;
auto command_buffer = &runtime->command_buffer;
auto plan = &runtime->plan;
command_buffer_reset(command_buffer);
plan_reset(plan);
for (size_t i = 0; i < result->count; i++) {
event_t event = {
.type = ZDWM_EVENT_WINDOW_MAP_REQUEST,
.as.window_map_request = result->windows[i],
};
policy_route_event(&ctx, &event, command_buffer);
}
policy_apply_command(&ctx, command_buffer, plan);
if (plan->need_relayout) runtime_arrange(runtime);
if (plan->count) backend_apply_effect(backend, plan->effects, plan->count);
backend_scan_result_destroy(result);
result = nullptr;
listeners_notify_initial_windows(&runtime->listeners, &runtime->state);
}
static void runtime_run_event_loop(runtime_t *runtime) {
runtime->running = true;
backend_t *backend = runtime->backend;
command_buffer_t *command_buffer = &runtime->command_buffer;
plan_t *plan = &runtime->plan;
policy_context_t ctx = policy_context_init(runtime);
while (runtime->running) {
event_t event = {0};
if (!backend_next_event(backend, &event)) break;
command_buffer_reset(command_buffer);
plan_reset(plan);
policy_route_event(&ctx, &event, command_buffer);
policy_apply_command(&ctx, command_buffer, plan);
if (plan->need_relayout) runtime_arrange(runtime);
if (plan->count) backend_apply_effect(backend, plan->effects, plan->count);
if (plan->quit) {
runtime->running = false;
runtime->will_restart = plan->will_restart;
}
event_cleanup(&event);
}
}
bool runtime_run(const char *config_so_path, const char *display_name) {
auto backend = backend_create(nullptr);
auto 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;
auto runtime = p_new(runtime_t, 1);
bool inited = runtime_init(runtime, &desc);
runtime_init_desc_cleanup(&desc);
backend_detect_destroy(detect);
if (!inited) {
fatal("runtime init failed");
}
runtime_setup(runtime);
runtime_scan(runtime);
runtime_run_event_loop(runtime);
auto restart = runtime->will_restart;
runtime_shutdown(runtime);
p_delete(&runtime);
return restart;
}