diff --git a/include/zdwm/action.h b/include/zdwm/action.h new file mode 100644 index 0000000..75a28fa --- /dev/null +++ b/include/zdwm/action.h @@ -0,0 +1,30 @@ +#ifndef ZDWM_ACTION_H +#define ZDWM_ACTION_H + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef union zdwm_action_arg_t { + bool b; + int32_t i; + uint32_t ui; + const char *str; +} zdwm_action_arg_t; + +typedef struct zdwm_action_ctx_t zdwm_action_ctx_t; + +typedef void +zdwm_action_fn(const zdwm_action_ctx_t *ctx, const zdwm_action_arg_t *arg); + +struct zdwm_action_ctx_t { + void (*spawn)(const char *command); +}; + +#if defined(__cplusplus) +} +#endif + +#endif /* ZDWM_ACTION_H */ diff --git a/include/zdwm/config.h b/include/zdwm/config.h index a81ca00..cc113b5 100644 --- a/include/zdwm/config.h +++ b/include/zdwm/config.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,68 @@ typedef struct zdwm_api_t { const zdwm_rule_match_t *match, const zdwm_rule_action_t *action ); + + /** + * @brief 用户调用这个函数添加按键模式 + * + * @details 按键模式名不能重复,如果添加重复的名字仅第一次成功,后续将添加失败 + * + * @param builder 配置构建上下文 + * @param mode_name 按键模式名字,不能为 nullptr 或空字符串 + * + * @return 添加成功返回新增模式的 id 否则返回 ZDWM_BINDING_MODE_ID_INVALID + */ + zdwm_binding_mode_id_t (*add_mode)( + zdwm_config_builder_t *builder, + const char *mode_name + ); + + /** + * @brief 添加按键绑定 + * + * @details mode_id 对应的模式不存在或 key_sequence 不合法,都会添加失败 + * + * @param builder 配置构建上下文 + * @param bind_mode 模式 ID + * @param key_sequence 按键序列的字符串表达 + * @param action_fn 用户行为函数 + * @param action_arg 用户行为函数所需的参数 + * + * @return 添加成功返回 true 否则返回 false + */ + bool (*bind)( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t bind_mode, + const char *key_sequence, + zdwm_action_fn action_fn, + zdwm_action_arg_t *action_arg + ); + + /** + * @brief 设置默认模式 + * + * @param table 按键绑定表 + * @param mode_id 按键模式 ID + * + * @return 切换成功返回 true 否则返回 false + */ + bool (*set_default_mode)( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t mode_id + ); + + /** + * @brief 设置初始模式 + * + * @param builder 配置构建上下文 + * @param mode_id 按键模式 ID + * + * @return 切换成功返回 true 否则返回 false + */ + bool (*set_initial_mode)( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t mode_id + ); } zdwm_api_t; /** diff --git a/include/zdwm/types.h b/include/zdwm/types.h index 7ee09f4..b5a3929 100644 --- a/include/zdwm/types.h +++ b/include/zdwm/types.h @@ -10,10 +10,12 @@ extern "C" { typedef uint32_t zdwm_layout_id_t; typedef uint32_t zdwm_window_id_t; typedef uint32_t zdwm_workspace_id_t; +typedef uint32_t zdwm_binding_mode_id_t; -#define ZDWM_LAYOUT_ID_INVALID ((zdwm_layout_id_t)UINT32_MAX) -#define ZDWM_WINDOW_ID_INVALID ((zdwm_window_id_t)0) -#define ZDWM_WORKSPACE_ID_INVALID ((zdwm_workspace_id_t)UINT32_MAX) +#define ZDWM_LAYOUT_ID_INVALID ((zdwm_layout_id_t)UINT32_MAX) +#define ZDWM_WINDOW_ID_INVALID ((zdwm_window_id_t)0) +#define ZDWM_WORKSPACE_ID_INVALID ((zdwm_workspace_id_t)UINT32_MAX) +#define ZDWM_BINDING_MODE_ID_INVALID ((zdwm_binding_mode_id_t)UINT32_MAX) typedef struct zdwm_rect_t { int32_t x; diff --git a/src/config/defaults.c b/src/config/defaults.c index 8a80946..d7449c0 100644 --- a/src/config/defaults.c +++ b/src/config/defaults.c @@ -1,7 +1,17 @@ #include "config/defaults.h" +#include +#include + #include "base/macros.h" +static constexpr char launcher[] = + "rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd"; + +static void spawn(const zdwm_action_ctx_t *ctx, const zdwm_action_arg_t *arg) { + ctx->spawn(arg->str); +} + bool config_defaults_build( const zdwm_api_t *api, zdwm_config_builder_t *builder, @@ -46,5 +56,20 @@ bool config_defaults_build( } } + auto default_mode = api->add_mode(builder, "default"); + + zdwm_action_arg_t launcher_arg = {.str = launcher}; + api->bind(builder, default_mode, "Mod4+r", spawn, &launcher_arg); + +#define BIND(mode, key, fn, arg) \ + api->bind(builder, mode, key, fn, &(zdwm_action_arg_t)arg) + + BIND(default_mode, "Mod4+r", spawn, {.str = launcher}); + +#undef BIND + + api->set_default_mode(builder, default_mode); + api->set_initial_mode(builder, default_mode); + return true; } diff --git a/src/config/runtime_config.c b/src/config/runtime_config.c index 60dfbff..b6dd74a 100644 --- a/src/config/runtime_config.c +++ b/src/config/runtime_config.c @@ -8,6 +8,7 @@ #include "base/memory.h" #include "config/defaults.h" #include "config/loader.h" +#include "core/binding.h" #include "core/layout.h" #include "core/rules.h" #include "core/runtime.h" @@ -21,6 +22,7 @@ struct zdwm_config_builder_t { size_t workspace_count; size_t workspace_capacity; size_t output_count; + binding_table_t *binding_table; }; void runtime_config_cleanup(runtime_init_desc_t *desc) { @@ -30,6 +32,8 @@ void runtime_config_cleanup(runtime_init_desc_t *desc) { desc->layouts.slot_capacity) { layout_registry_cleanup(&desc->layouts); } + binding_table_destroy(desc->binding_table); + desc->binding_table = nullptr; workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count); if (desc->config_module_handle) dlclose(desc->config_module_handle); desc->config_module_handle = nullptr; @@ -45,6 +49,9 @@ static void config_builder_cleanup(zdwm_config_builder_t *builder) { workspace_desc_list_cleanup(&builder->workspaces, &builder->workspace_count); builder->workspace_capacity = 0; builder->output_count = 0; + + binding_table_destroy(builder->binding_table); + builder->binding_table = nullptr; } static layout_id_t runtime_config_register_layout( @@ -144,6 +151,35 @@ static bool runtime_config_add_rule( return true; } +static zdwm_binding_mode_id_t +runtime_config_add_mode(zdwm_config_builder_t *builder, const char *mode_name) { + return binding_table_add_mode(builder->binding_table, mode_name); +} + +static bool runtime_config_bind( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t mode, + const char *key, + zdwm_action_fn fn, + zdwm_action_arg_t *arg +) { + return binding_table_add_bind(builder->binding_table, mode, key, fn, arg); +} + +static bool runtime_config_set_default_mode( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t mode_id +) { + return binding_table_set_default_mode(builder->binding_table, mode_id); +} + +static bool runtime_config_set_initial_mode( + zdwm_config_builder_t *builder, + zdwm_binding_mode_id_t mode_id +) { + return binding_table_set_current_mode(builder->binding_table, mode_id); +} + static bool config_builder_finish( zdwm_config_builder_t *builder, runtime_init_desc_t *out @@ -154,8 +190,10 @@ static bool config_builder_finish( if (!layout_registry_move(&builder->layouts, &out->layouts)) return false; if (!rules_move(&builder->rules, &out->rules)) return false; + out->binding_table = builder->binding_table; out->workspaces = builder->workspaces; out->workspace_count = builder->workspace_count; + builder->binding_table = nullptr; builder->workspaces = nullptr; builder->workspace_count = 0; builder->workspace_capacity = 0; @@ -172,6 +210,7 @@ static bool runtime_config_build( if (!setup || !out) return false; zdwm_config_builder_t builder = {0}; builder.output_count = output_count; + builder.binding_table = binding_table_create(); zdwm_api_t api = { .abi_version = ZDWM_CONFIG_ABI_VERSION, @@ -183,6 +222,10 @@ static bool runtime_config_build( .register_layout = runtime_config_register_layout, .define_workspace = runtime_config_define_workspace, .add_rule = runtime_config_add_rule, + .add_mode = runtime_config_add_mode, + .bind = runtime_config_bind, + .set_default_mode = runtime_config_set_default_mode, + .set_initial_mode = runtime_config_set_initial_mode, }; bool ok = setup(&api, &builder, outputs, output_count) && config_builder_finish(&builder, out); diff --git a/src/core/action.c b/src/core/action.c new file mode 100644 index 0000000..f4308ff --- /dev/null +++ b/src/core/action.c @@ -0,0 +1,23 @@ +#include "core/action.h" + +#include +#include +#include +#include + +#include "base/log.h" + +void spawn(const char *command) { + if (fork() == 0) { + setsid(); + + if (fork() == 0) { + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, nullptr); + fatal("execl fail: %s", command); + } + + exit(0); + } + + wait(0); +} diff --git a/src/core/action.h b/src/core/action.h new file mode 100644 index 0000000..e1b1c21 --- /dev/null +++ b/src/core/action.h @@ -0,0 +1,3 @@ +#pragma once + +void spawn(const char *command); diff --git a/src/core/binding.c b/src/core/binding.c new file mode 100644 index 0000000..17e23ee --- /dev/null +++ b/src/core/binding.c @@ -0,0 +1,194 @@ +#include "core/binding.h" + +#include +#include +#include +#include +#include +#include + +#include "base/array.h" +#include "base/memory.h" +#include "core/event.h" + +typedef struct key_binding_t { + const char *key_str; + modifier_mask_t modifiers; + xkb_keysym_t keysym; + zdwm_action_fn *fn; + zdwm_action_arg_t *arg; +} key_binding_t; + +typedef struct binding_mode_t { + zdwm_binding_mode_id_t id; + const char *name; + key_binding_t *items; + size_t count; + size_t capacity; +} binding_mode_t; + +typedef struct binding_table_t { + binding_mode_t *modes; + size_t count; + size_t capacity; + zdwm_binding_mode_id_t default_mode; + zdwm_binding_mode_id_t current_mode; +} binding_table_t; + +zdwm_binding_mode_id_t +binding_table_add_mode(binding_table_t *table, const char *mode_name) { + if (!table || !mode_name || strlen(mode_name) == 0) { + return ZDWM_BINDING_MODE_ID_INVALID; + } + + for (size_t i = 0; i < table->count; ++i) { + if (strcmp(table->modes[i].name, mode_name) == 0) { + return ZDWM_BINDING_MODE_ID_INVALID; + } + } + + zdwm_binding_mode_id_t id = (zdwm_binding_mode_id_t)table->count; + binding_mode_t *new_mode = + array_push(table->modes, table->count, table->capacity); + new_mode->name = mode_name; + new_mode->id = id; + + return id; +} + +static binding_mode_t * +binding_table_get_mode(binding_table_t *table, zdwm_binding_mode_id_t mode_id) { + if (mode_id >= table->count) return nullptr; + + binding_mode_t *mode = &table->modes[mode_id]; + if (mode->id == mode_id) return mode; + + for (size_t i = 0; i < table->count; ++i) { + mode = &table->modes[i]; + if (mode->id == mode_id) return mode; + } + + return nullptr; +} + +/** + * @brief 解析按键序列 + * + * @details 按键序列不合法解析失败,解析成功会设置 modifiers 与 keysym 对应的值 + * + * @param key_sequence 按键序列 + * @param modifiers 返回修饰键的指针 + * @param keysym 返回按键符号的指针 + * + * @return 解析成功返回 true 否则返回 false + */ +bool parse_key_sequence( + const char *key_sequence, + modifier_mask_t *modifiers, + xkb_keysym_t *keysym +) { + /* TODO: */ + return false; +} + +bool binding_table_add_bind( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id, + const char *key_sequence, + zdwm_action_fn fn, + zdwm_action_arg_t *arg +) { + binding_mode_t *mode = binding_table_get_mode(table, mode_id); + if (!mode) return false; + + modifier_mask_t modifiers = 0; + xkb_keysym_t keysym = XKB_KEY_NoSymbol; + if (!parse_key_sequence(key_sequence, &modifiers, &keysym)) return false; + + key_binding_t *item = array_push(mode->items, mode->count, mode->capacity); + item->key_str = key_sequence; + item->modifiers = modifiers; + item->keysym = keysym; + item->fn = fn; + item->arg = arg; + + return true; +} + +void binding_table_destroy(binding_table_t *table) { + if (!table) return; + + for (size_t i = 0; i < table->count; ++i) { + binding_mode_t *mode = &table->modes[i]; + p_delete(&mode->items); + mode->count = 0; + mode->capacity = 0; + } + + p_delete(&table->modes); + table->count = 0; + table->capacity = 0; + table->default_mode = ZDWM_BINDING_MODE_ID_INVALID; + + p_delete(&table); +} + +binding_table_t *binding_table_create(void) { + binding_table_t *table = p_new(binding_table_t, 1); + + /* 默认情况下,默认模式和当前模式都是第一个添加的模式 */ + table->default_mode = 0; + table->current_mode = 0; + + return table; +} + +bool binding_table_set_default_mode( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id +) { + if (mode_id >= table->count) return false; + + table->default_mode = mode_id; + return true; +} + +bool binding_table_set_current_mode( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id +) { + if (mode_id >= table->count) return false; + + table->current_mode = mode_id; + return true; +} + +bool binding_table_cycle_mode(binding_table_t *table, int delta) { + if (!table || !table->modes || table->count < 1) return false; + + bool matched = false; + size_t index = (size_t)table->current_mode; + if (table->modes[index].id == table->current_mode) { + matched = true; + } else { + for (size_t i = 0; i < table->count; ++i) { + if (table->modes[i].id == table->current_mode) { + index = i; + matched = true; + break; + } + } + } + + if (!matched) return false; + + /* 只有一个模式没必要切换 */ + if (table->count == 1) return true; + + int64_t count = (int64_t)table->count; + int64_t next_index = ((int64_t)index + (int64_t)delta) % count; + if (next_index < 0) next_index += count; + table->current_mode = table->modes[next_index].id; + + return true; +} diff --git a/src/core/binding.h b/src/core/binding.h new file mode 100644 index 0000000..277ed30 --- /dev/null +++ b/src/core/binding.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +typedef struct binding_table_t binding_table_t; + +/** + * @brief 添加新的模式 + * + * @details 按键模式名不能重复,如果添加重复的名字仅第一次成功,后续将添加失败 + * + * @param table 按键绑定表 + * @param mode_name 按键模式名字,不能为 nullptr 或空字符串 + * + * @return 添加成功返回新增模式的 id 否则返回 ZDWM_BINDING_MODE_ID_INVALID + */ +zdwm_binding_mode_id_t +binding_table_add_mode(binding_table_t *table, const char *mode_name); + +/** + * @brief 添加按键绑定 + * + * @details mode_id 对应的模式不存在或 key_sequence 不合法,都会添加失败 + * + * @param table 按键绑定表 + * @param mode_id 模式 ID + * @param key_sequence 按键序列的字符串表达 + * @param fn 用户行为函数 + * @param arg 用户行为函数所需的参数 + * + * @return 添加成功返回 true 否则返回 false + */ +bool binding_table_add_bind( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id, + const char *key_sequence, + zdwm_action_fn fn, + zdwm_action_arg_t *arg +); + +/** + * @brief 销毁按键绑定表 + */ +void binding_table_destroy(binding_table_t *table); + +/** + * @brief 创建按键绑定表 + */ +binding_table_t *binding_table_create(void); + +/** + * @brief 通过模式 ID 设置默认模式 + * + * @param table 按键绑定表 + * @param mode_id 按键模式 ID + * + * @return 切换成功返回 true 否则返回 false + */ +bool binding_table_set_default_mode( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id +); + +/** + * @brief 通过模式 ID 设置当前模式 + * + * @param table 按键绑定表 + * @param mode_id 按键模式 ID + * + * @return 切换成功返回 true 否则返回 false + */ +bool binding_table_set_current_mode( + binding_table_t *table, + zdwm_binding_mode_id_t mode_id +); + +/** + * @brief 切换按键绑定模式 + * + * @param table 按键绑定表 + * @param delta 切换变化量 + * + * @return 无法切换返回 false 否则返回 true + */ +bool binding_table_cycle_mode(binding_table_t *table, int delta); diff --git a/src/core/policy.h b/src/core/policy.h index f2d0395..26d6a10 100644 --- a/src/core/policy.h +++ b/src/core/policy.h @@ -1,5 +1,6 @@ #pragma once +#include #include "core/command_buffer.h" #include "core/event.h" #include "core/plan.h" @@ -9,6 +10,7 @@ typedef struct policy_context_t { state_t *state; const rules_t *rules; + const zdwm_action_ctx_t action_ctx; } policy_context_t; /** diff --git a/src/core/runtime.c b/src/core/runtime.c index e669e93..7f56906 100644 --- a/src/core/runtime.c +++ b/src/core/runtime.c @@ -2,8 +2,10 @@ #include +#include "action.h" #include "base/memory.h" #include "core/backend.h" +#include "core/binding.h" #include "core/command_buffer.h" #include "core/event.h" #include "core/layout.h" @@ -52,8 +54,10 @@ bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc) { layout_registry_move(&desc->layouts, &runtime->layouts); rules_move(&desc->rules, &runtime->rules); runtime->config_module_handle = desc->config_module_handle; + runtime->binding_table = desc->binding_table; desc->backend = nullptr; desc->config_module_handle = nullptr; + desc->binding_table = nullptr; state_init( &runtime->state, @@ -73,6 +77,9 @@ bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc) { void runtime_init_desc_cleanup(runtime_init_desc_t *desc) { if (!desc) return; + binding_table_destroy(desc->binding_table); + desc->binding_table = nullptr; + if (desc->backend) { backend_destroy(desc->backend); desc->backend = nullptr; @@ -97,6 +104,8 @@ void runtime_shutdown(runtime_t *runtime) { layout_registry_cleanup(&runtime->layouts); rules_cleanup(&runtime->rules); state_cleanup(&runtime->state); + binding_table_destroy(runtime->binding_table); + runtime->binding_table = nullptr; backend_destroy(runtime->backend); runtime->backend = nullptr; if (runtime->config_module_handle) dlclose(runtime->config_module_handle); @@ -111,8 +120,11 @@ void runtime_run(runtime_t *runtime) { plan_t *plan = &runtime->plan; policy_context_t ctx = { - .state = &runtime->state, - .rules = &runtime->rules, + .state = &runtime->state, + .rules = &runtime->rules, + .action_ctx = { + .spawn = spawn, + }, }; while (runtime->running) { diff --git a/src/core/runtime.h b/src/core/runtime.h index b3cb140..2bfeae0 100644 --- a/src/core/runtime.h +++ b/src/core/runtime.h @@ -3,6 +3,7 @@ #include #include "core/backend.h" +#include "core/binding.h" #include "core/command_buffer.h" #include "core/layout.h" #include "core/plan.h" @@ -19,6 +20,7 @@ typedef struct runtime_init_desc_t { workspace_desc_t *workspaces; size_t workspace_count; void *config_module_handle; + binding_table_t *binding_table; } runtime_init_desc_t; typedef struct runtime_t { @@ -32,6 +34,7 @@ typedef struct runtime_t { rules_t rules; backend_t *backend; void *config_module_handle; + binding_table_t *binding_table; } runtime_t; bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc); diff --git a/tests/config/CMakeLists.txt b/tests/config/CMakeLists.txt index 7951732..a1a276c 100644 --- a/tests/config/CMakeLists.txt +++ b/tests/config/CMakeLists.txt @@ -41,6 +41,8 @@ add_executable(${RUNTIME_CONFIG_TEST_APP_NAME} ${SOURCE_DIR}/config/defaults.c ${SOURCE_DIR}/config/loader.c ${SOURCE_DIR}/config/runtime_config.c + ${SOURCE_DIR}/core/action.c + ${SOURCE_DIR}/core/binding.c ${SOURCE_DIR}/core/command_buffer.c ${SOURCE_DIR}/core/event.c ${SOURCE_DIR}/core/layer.c diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index c2a79e4..a3a0c5d 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -10,6 +10,8 @@ add_executable(${TEST_APP_NAME} ${SOURCE_DIR}/backend/x11/backend.c ${SOURCE_DIR}/backend/x11/event.c ${SOURCE_DIR}/backend/x11/window.c + ${SOURCE_DIR}/core/action.c + ${SOURCE_DIR}/core/binding.c ${SOURCE_DIR}/core/command_buffer.c ${SOURCE_DIR}/core/event.c ${SOURCE_DIR}/core/layer.c