From 8153a24863369f4c805f611c1aab6704de92d41b Mon Sep 17 00:00:00 2001 From: Keith Bowes Date: Tue, 1 Feb 2022 20:26:50 -0500 Subject: [PATCH] Basic config-file support --- README.md | 1 + include/waybox/server.h | 4 + meson.build | 1 + waybox/config.c | 185 ++++++++++++++++++++++++++++++++++++++++ waybox/config.h | 30 +++++++ waybox/decoration.h | 1 - waybox/main.c | 11 ++- waybox/meson.build | 2 + waybox/seat.c | 123 ++++++++++++++++---------- waybox/server.c | 2 + 10 files changed, 312 insertions(+), 48 deletions(-) create mode 100644 waybox/config.c create mode 100644 waybox/config.h diff --git a/README.md b/README.md index 5686160..39923e4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ contributing.](https://github.com/wizbright/waybox/blob/master/CONTRIBUTING.md) * [Meson](https://mesonbuild.com/) * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots/) +* [libxml2](http://xmlsoft.org/) * [Wayland](https://wayland.freedesktop.org/) * [xkbcommon](https://xkbcommon.org/) diff --git a/include/waybox/server.h b/include/waybox/server.h index e253100..092618e 100644 --- a/include/waybox/server.h +++ b/include/waybox/server.h @@ -25,6 +25,7 @@ #include #define _ gettext +#include "config.h" #include "waybox/cursor.h" #include "decoration.h" #include "layer_shell.h" @@ -40,6 +41,9 @@ struct wb_server { struct wlr_output_layout *output_layout; struct wlr_renderer *renderer; + struct wb_config *config; + char *config_file; + struct wb_cursor *cursor; struct wb_seat *seat; diff --git a/meson.build b/meson.build index 100abf1..9c846c2 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,7 @@ inc_dir = include_directories('include') # Due to the planned refactor of xdg_shell in wlroots 0.16.0, I doubt this will # build when it's released +libxml2 = dependency('libxml-2.0') wlroots = dependency('wlroots', version: ['>=0.15.0', '<0.16.0']) wayland_server = dependency('wayland-server', version: '>=1.15') wayland_protos = dependency('wayland-protocols', version: '>=1.17') diff --git a/waybox/config.c b/waybox/config.c new file mode 100644 index 0000000..eb7a017 --- /dev/null +++ b/waybox/config.c @@ -0,0 +1,185 @@ +#include +#include + +#include "config.h" + +static char *parse_xpath_expr(char *expr, xmlXPathContextPtr ctxt) { + xmlXPathObjectPtr object = xmlXPathEvalExpression((xmlChar *) expr, ctxt); + if (object == NULL) { + wlr_log(WLR_INFO, "%s", _("Unable to evaluate expression")); + xmlXPathFreeContext(ctxt); + return(NULL); + } + if (!object->nodesetval) { + wlr_log(WLR_INFO, "%s", _("No nodesetval")); + return NULL; + } + xmlChar *ret = NULL; + if (object->nodesetval->nodeNr > 0) + ret = xmlNodeGetContent(object->nodesetval->nodeTab[0]); + xmlXPathFreeObject(object); + return (char *) ret; +} + +static bool parse_key_bindings(struct wb_config *config, xmlXPathContextPtr ctxt) +{ + /* Get the key bindings */ + wl_list_init(&config->key_bindings); + xmlXPathObjectPtr object = xmlXPathEvalExpression((xmlChar *) "//ob:keyboard/ob:keybind", ctxt); + if (object == NULL) { + wlr_log(WLR_INFO, "%s", _("Unable to evaluate expression")); + return(false); + } + if (!object->nodesetval) { + wlr_log(WLR_INFO, "%s", _("No nodeset")); + return false; + } + + /* Iterate through the nodes to get the information */ + if (object->nodesetval->nodeNr > 0) { + int i; + for (i = 0; i < object->nodesetval->nodeNr; i++) { + if (object->nodesetval->nodeTab[i]) { + /* First get the key combinations */ + xmlAttr *keycomb = object->nodesetval->nodeTab[i]->properties; + while (strcmp((char *) keycomb->name, "key") != 0) + { + keycomb = keycomb->next; + } + + char *sym; + uint32_t modifiers = 0; + sym = (char *) keycomb->children->content; + char *s; + + struct wb_key_binding *key_bind = calloc(1, sizeof(struct wb_key_binding)); + key_bind->sym = 0; + key_bind->modifiers = 0; + while ((s = strtok(sym, "-")) != NULL) + { + if (strcmp(s, "A") == 0 || strcmp(s, "Alt") == 0) + modifiers |= WLR_MODIFIER_ALT; + else if (strcmp(s, "Caps") == 0) + modifiers |= WLR_MODIFIER_CAPS; + else if (strcmp(s, "C") == 0 || strcmp(s, "Ctrl") == 0) + modifiers |= WLR_MODIFIER_CTRL; + else if (strcmp(s, "Mod2") == 0) + modifiers |= WLR_MODIFIER_MOD2; + else if (strcmp(s, "Mod3") == 0) + modifiers |= WLR_MODIFIER_MOD3; + else if (strcmp(s, "Mod5") == 0) + modifiers |= WLR_MODIFIER_MOD5; + else if (strcmp(s, "S") == 0 || strcmp(s, "Shift") == 0) + modifiers |= WLR_MODIFIER_SHIFT; + else if (strcmp(s, "W") == 0 || strcmp(s, "Logo") == 0) + modifiers |= WLR_MODIFIER_LOGO; + else + key_bind->sym = xkb_keysym_from_name(s, 0); + key_bind->modifiers = modifiers; + sym = NULL; + } + + /* Now get the actions */ + xmlNode *cur_node; + xmlNode *new_node = object->nodesetval->nodeTab[i]->children; + xmlAttr *attr; + for (cur_node = new_node; cur_node; cur_node = cur_node->next) + { + if (strcmp((char *) cur_node->name, "action") == 0) + { + attr = cur_node->properties; + while (strcmp((char *) attr->name, "name") != 0) + { + attr = attr->next; + } + key_bind->action = malloc(sizeof (char) * 64); + strcpy(key_bind->action, (char *) attr->children->content); + wlr_log(WLR_INFO, "Registering action %s", (char *) key_bind->action); + if (strcmp((char *) key_bind->action, "Execute") != 0) + break; + cur_node = cur_node->children; + } + if (strcmp((char *) cur_node->name, "execute") == 0) + { + /* Bad things can happen if the command is greater than 1024 characters */ + key_bind->cmd = malloc(sizeof(char) * 1024); + strncpy(key_bind->cmd, (char *) cur_node->children->content, 1023); + key_bind->cmd[strlen(key_bind->cmd)] = '\0'; + if (key_bind->action) + break; + } + } + + wl_list_insert(&config->key_bindings, &key_bind->link); + } + } + } + xmlXPathFreeObject(object); + return true; +} + +bool init_config(struct wb_server *server) +{ + xmlDocPtr doc; + if (server->config_file == NULL) { + char *xdg_config = getenv("XDG_CONFIG_HOME"); + if (!xdg_config) + xdg_config = "~/.config"; + + char *rc_file = malloc(strlen(xdg_config) + 14); + strcpy(rc_file, xdg_config); + rc_file = strcat(rc_file, "/waybox/rc.xml"); + doc = xmlParseFile(rc_file); + wlr_log(WLR_INFO, "Using config file %s", rc_file); + free(rc_file); + } else { + wlr_log(WLR_INFO, "Using config file %s", server->config_file); + doc = xmlParseFile(server->config_file); + } + + if (doc == NULL) { + wlr_log(WLR_INFO, "%s", _("Unable to parse XML file")); + return false; + } + xmlXPathContextPtr ctxt = xmlXPathNewContext(doc); + if (ctxt == NULL) { + wlr_log(WLR_INFO, "%s", _("Couldn't create new context!")); + xmlFreeDoc(doc); + return(false); + } + if (xmlXPathRegisterNs(ctxt, (const xmlChar *) "ob", (const xmlChar *) "http://openbox.org/3.4/rc") != 0) { + wlr_log(WLR_INFO, "%s", _("Couldn't register the namespace")); + } + + struct wb_config *config = calloc(1, sizeof(struct wb_config)); + config->keyboard_layout.layout = parse_xpath_expr("//ob:keyboard//ob:layout//ob:layout", ctxt); + config->keyboard_layout.model = parse_xpath_expr("//ob:keyboard//ob:layout//ob:model", ctxt); + config->keyboard_layout.options = parse_xpath_expr("//ob:keyboard//ob:layout//ob:options", ctxt); + config->keyboard_layout.rules = parse_xpath_expr("//ob:keyboard//ob:layout//ob:rules", ctxt); + config->keyboard_layout.variant = parse_xpath_expr("//ob:keyboard//ob:layout//ob:variant", ctxt); + if (!parse_key_bindings(config, ctxt)) + { + xmlFreeDoc(doc); + return false; + } + server->config = config; + + xmlXPathFreeContext(ctxt); + xmlFreeDoc(doc); + xmlCleanupParser(); + + return true; +} + +void deinit_config(struct wb_config *config) +{ + /* Free everything allocated in init_config */ + struct wb_key_binding *key_binding; + wl_list_for_each(key_binding, &config->key_bindings, link) { + free(key_binding->action); + free(key_binding->cmd); + free(key_binding); + } + wl_list_remove(&config->key_bindings); + free(config); +} diff --git a/waybox/config.h b/waybox/config.h new file mode 100644 index 0000000..c3805a6 --- /dev/null +++ b/waybox/config.h @@ -0,0 +1,30 @@ +#ifndef _WB_CONFIG_H +#define _WB_CONFIG_H + +#include "waybox/server.h" + +struct wb_config { + struct wb_server *server; + struct { + char *layout; + char *model; + char *options; + char *rules; + char *variant; + } keyboard_layout; + + struct wl_list applications; + struct wl_list key_bindings; +}; + +struct wb_key_binding { + xkb_keysym_t sym; + uint32_t modifiers; + char *action; + char *cmd; + struct wl_list link; +}; + +bool init_config(struct wb_server *server); +void deinit_config(struct wb_config *config); +#endif diff --git a/waybox/decoration.h b/waybox/decoration.h index a079511..5e68bba 100644 --- a/waybox/decoration.h +++ b/waybox/decoration.h @@ -13,7 +13,6 @@ struct wb_decoration { struct wl_listener toplevel_decoration_destroy; struct wl_listener request_mode; struct wl_listener mode_destroy; - int yes; }; void init_xdg_decoration(struct wb_server *server); diff --git a/waybox/main.c b/waybox/main.c index d8590de..56bd22a 100644 --- a/waybox/main.c +++ b/waybox/main.c @@ -26,6 +26,7 @@ int main(int argc, char **argv) { textdomain(GETTEXT_PACKAGE); char *startup_cmd = NULL; + struct wb_server server = {0}; enum wlr_log_importance debuglevel = WLR_ERROR; if (argc > 1) { int i; @@ -44,8 +45,13 @@ int main(int argc, char **argv) { } else if (!strcmp("--help", argv[i]) || !strcmp("-h", argv[i])) { show_help(argv[0]); return 0; - } else if (!strcmp("--config-file", argv[i]) || - !strcmp("--sm-disable", argv[i])) { + } else if (strcmp("--config-file", argv[i]) == 0) { + if (i < argc - 1) { + server.config_file = argv[i + 1]; + } else { + fprintf(stderr, _("%s requires an argument\n"), argv[i]); + } + } else if (!strcmp("--sm-disable", argv[i])) { fprintf(stderr, _("%s hasn't been implemented yet.\n"), argv[i]); if (i == argc - 1) { fprintf(stderr, _("%s requires an argument\n"), argv[i]); @@ -58,7 +64,6 @@ int main(int argc, char **argv) { } wlr_log_init(debuglevel, NULL); - struct wb_server server = {0}; if (wb_create_backend(&server)) { wlr_log(WLR_INFO, "%s", _("Successfully created backend")); diff --git a/waybox/meson.build b/waybox/meson.build index 4e12aed..eacad8b 100644 --- a/waybox/meson.build +++ b/waybox/meson.build @@ -1,4 +1,5 @@ wb_src = files( + 'config.c', 'cursor.c', 'decoration.c', 'layer_shell.c', @@ -10,6 +11,7 @@ wb_src = files( ) wb_dep = [ + libxml2, wayland_server, wlroots, xkbcommon, diff --git a/waybox/seat.c b/waybox/seat.c index 0e47578..ce3cf26 100644 --- a/waybox/seat.c +++ b/waybox/seat.c @@ -8,45 +8,63 @@ static bool handle_keybinding(struct wb_server *server, xkb_keysym_t sym, uint32 * Here we handle compositor keybindings. This is when the compositor is * processing keys, rather than passing them on to the client for its own * processing. + * + * Returns true if the keybinding is handled, false to send it to the + * client. */ - if (modifiers & WLR_MODIFIER_CTRL && sym == XKB_KEY_Escape) { - wl_display_terminate(server->wl_display); - } else if (modifiers & WLR_MODIFIER_ALT && sym == XKB_KEY_Tab) { - /* Cycle to the next view */ - if (wl_list_length(&server->views) < 2) { - return false; + struct wb_key_binding *key_binding; + wl_list_for_each(key_binding, &server->config->key_bindings, link) { + if (sym == key_binding->sym && modifiers == key_binding->modifiers) + { + if ((strcmp("NextWindow", key_binding->action) == 0)) { + /* Cycle to the next view */ + if (wl_list_length(&server->views) < 2) { + return false; + } + struct wb_view *current_view = wl_container_of( + server->views.prev, current_view, link); + struct wb_view *prev_view = wl_container_of( + server->views.next, prev_view, link); + focus_view(prev_view, prev_view->xdg_surface->surface); + /* Move the current view to the beginning of the list */ + wl_list_remove(¤t_view->link); + wl_list_insert(&server->views, ¤t_view->link); + return true; + } + else if ((strcmp("PreviousWindow", key_binding->action) == 0)) { + /* Cycle to the previous view */ + if (wl_list_length(&server->views) < 2) { + return false; + } + struct wb_view *current_view = wl_container_of( + server->views.next, current_view, link); + struct wb_view *next_view = wl_container_of( + current_view->link.next, next_view, link); + focus_view(next_view, next_view->xdg_surface->surface); + /* Move the previous view to the end of the list */ + wl_list_remove(¤t_view->link); + wl_list_insert(server->views.prev, ¤t_view->link); + return true; + } + else if ((strcmp("Close", key_binding->action) == 0)) { + struct wb_view *current_view = wl_container_of( + server->views.next, current_view, link); + wlr_xdg_toplevel_send_close(current_view->xdg_surface); + } + else if ((strcmp("Execute", key_binding->action) == 0)) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", key_binding->cmd, (char *) NULL); + } + return true; + } + else if ((strcmp("Exit", key_binding->action) == 0)) { + wl_display_terminate(server->wl_display); + return true; + } } - struct wb_view *current_view = wl_container_of( - server->views.next, current_view, link); - struct wb_view *next_view = wl_container_of( - current_view->link.next, next_view, link); - focus_view(next_view, next_view->xdg_surface->surface); - /* Move the previous view to the end of the list */ - wl_list_remove(¤t_view->link); - wl_list_insert(server->views.prev, ¤t_view->link); - } else if ((modifiers & (WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT)) - && sym == XKB_KEY_ISO_Left_Tab) { - /* Cycle to the previous view */ - if (wl_list_length(&server->views) < 2) { - return false; - } - struct wb_view *current_view = wl_container_of( - server->views.prev, current_view, link); - struct wb_view *prev_view = wl_container_of( - server->views.next, prev_view, link); - focus_view(prev_view, prev_view->xdg_surface->surface); - /* Move the current view to the beginning of the list */ - wl_list_remove(¤t_view->link); - wl_list_insert(&server->views, ¤t_view->link); - } else if (modifiers & WLR_MODIFIER_ALT && sym == XKB_KEY_F2) { - if (fork() == 0) { - execl("/bin/sh", "/bin/sh", "-c", "(obrun || bemenu-run || synapse || gmrun || gnome-do || dmenu_run) 2>/dev/null", (char *) NULL); - } - } else { - return false; } - return true; + return false; } static void keyboard_handle_modifiers( @@ -107,21 +125,38 @@ static void handle_new_keyboard(struct wb_server *server, keyboard->device = device; /* We need to prepare an XKB keymap and assign it to the keyboard. */ - struct xkb_rule_names rules = { - .rules = getenv("XKB_DEFAULT_RULES"), - .model = getenv("XKB_DEFAULT_MODEL"), - .layout = getenv("XKB_DEFAULT_LAYOUT"), - .variant = getenv("XKB_DEFAULT_VARIANT"), - .options = getenv("XKB_DEFAULT_OPTIONS"), - }; + struct xkb_rule_names rules = {0}; + if (server->config->keyboard_layout.layout) + rules.layout = server->config->keyboard_layout.layout; + else + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + if (server->config->keyboard_layout.model) + rules.model = server->config->keyboard_layout.model; + else + rules.model = getenv("XKB_DEFAULT_MODEL"); + if (server->config->keyboard_layout.options) + rules.options = server->config->keyboard_layout.options; + else + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + if (server->config->keyboard_layout.rules) + rules.rules = server->config->keyboard_layout.rules; + else + rules.variant = getenv("XKB_DEFAULT_RULES"); + if (server->config->keyboard_layout.variant) + rules.variant = server->config->keyboard_layout.variant; + else + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - wlr_keyboard_set_keymap(device->keyboard, keymap); + if (keymap != NULL) { + wlr_keyboard_set_keymap(device->keyboard, keymap); + wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); + } xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(device->keyboard, 25, 600); /* Here we set up listeners for keyboard events. */ keyboard->modifiers.notify = keyboard_handle_modifiers; diff --git a/waybox/server.c b/waybox/server.c index dc1331f..8e38192 100644 --- a/waybox/server.c +++ b/waybox/server.c @@ -42,6 +42,7 @@ bool wb_create_backend(struct wb_server* server) { } bool wb_start_server(struct wb_server* server) { + init_config(server); wl_list_init(&server->outputs); server->new_output.notify = new_output_notify; @@ -80,6 +81,7 @@ bool wb_terminate(struct wb_server* server) { wb_cursor_destroy(server->cursor); wl_list_remove(&server->new_xdg_decoration.link); /* wb_decoration_destroy */ wb_seat_destroy(server->seat); + deinit_config(server->config); wl_display_destroy_clients(server->wl_display); wl_display_destroy(server->wl_display); wlr_output_layout_destroy(server->output_layout);