493 lines
14 KiB
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;
|
|
}
|