From 180b16faee4b4fdba493d6ba7a5b4ba6e0475ead Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Wed, 7 Aug 2024 15:20:34 +0200 Subject: wmstatus: add an option to import bindings to Sway We still want to retain the ability to bind them on our own under X11. With this, the Wayland situation has considerably improved, but the activity watch and keyboard layout switching are still broken. --- wmstatus.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 3 deletions(-) diff --git a/wmstatus.c b/wmstatus.c index 11cce10..426fb0d 100644 --- a/wmstatus.c +++ b/wmstatus.c @@ -70,6 +70,20 @@ log_message_custom (void *user_data, const char *quote, const char *fmt, fputs ("\n", stream); } +static void +shell_quote (const char *str, struct str *output) +{ + // See SUSv3 Shell and Utilities, 2.2.3 Double-Quotes + str_append_c (output, '"'); + for (const char *p = str; *p; p++) + { + if (strchr ("`$\"\\", *p)) + str_append_c (output, '\\'); + str_append_c (output, *p); + } + str_append_c (output, '"'); +} + // --- NUT --------------------------------------------------------------------- // More or less copied and pasted from the MPD client. This code doesn't even @@ -2992,6 +3006,136 @@ app_save_configuration (struct app_context *ctx, const char *path_hint) free (filename); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +sway_command_argument_needs_quoting (const char *word) +{ + while (*word) + if (!isalnum_ascii (*word++)) + return true; + return false; +} + +static void +sway_append_command_argument (struct str *out, const char *word) +{ + if (out->len) + str_append_c (out, ' '); + + if (!sway_command_argument_needs_quoting (word)) + { + str_append (out, word); + return; + } + + str_append_c (out, '\''); + for (const char *p = word; *p; p++) + { + if (*p == '\'' || *p == '\\') + str_append_c (out, '\\'); + str_append_c (out, *p); + } + str_append_c (out, '\''); +} + +static const char * +sway_bindsym (const char *combination, const char *action) +{ + const char *error = NULL; + struct strv keys = strv_make (); + struct strv args = strv_make (); + if (!parse_binding (combination, &keys)) + { + error = "parsing key combination failed"; + goto out; + } + if (!parse_binding (action, &args) || !args.len) + { + error = "parsing the binding failed"; + goto out; + } + + struct action handler = action_by_name (args.vector[0]); + if (!handler.name) + { + error = "unknown action"; + goto out; + } + + // The i3/Sway quoting is properly fucked up, + // and its exec command forwards to `sh -c`. + struct str shell_command = str_make (); + if (strcmp (handler.name, "exec")) + { + // argv[0] would need realpath() applied on it. + shell_quote (PROGRAM_NAME, &shell_command); + str_append (&shell_command, " -- "); + shell_quote (handler.name, &shell_command); + str_append_c (&shell_command, ' '); + } + for (size_t i = 1; i < args.len; i++) + { + shell_quote (args.vector[i], &shell_command); + str_append_c (&shell_command, ' '); + } + if (shell_command.len) + shell_command.str[--shell_command.len] = 0; + + // This command name may not be quoted. + // Note that i3-msg doesn't accept bindsym at all, only swaymsg does. + struct str sway_command = str_make (); + sway_append_command_argument (&sway_command, "bindsym"); + char *recombined = strv_join (&keys, "+"); + sway_append_command_argument (&sway_command, recombined); + free (recombined); + sway_append_command_argument (&sway_command, "exec"); + sway_append_command_argument (&sway_command, shell_command.str); + str_free (&shell_command); + + struct strv argv = strv_make (); + strv_append (&argv, "swaymsg"); + strv_append_owned (&argv, str_steal (&sway_command)); + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init (&actions); + posix_spawnp (NULL, argv.vector[0], &actions, NULL, argv.vector, environ); + posix_spawn_file_actions_destroy (&actions); + + strv_free (&argv); +out: + strv_free (&keys); + strv_free (&args); + return error; +} + +static void +sway_forward_bindings (void) +{ + // app_context_init() has side-effects. + struct app_context ctx = { .config = app_make_config () }; + app_load_configuration (&ctx); + + struct str_map *keys = + &config_item_get (ctx.config.root, "keys", NULL)->value.object; + struct str_map_iter iter = str_map_iter_make (keys); + + struct config_item *action; + while ((action = str_map_iter_next (&iter))) + { + const char *combination = iter.link->key, *err = NULL; + if (action->type != CONFIG_ITEM_NULL) + { + if (action->type != CONFIG_ITEM_STRING) + err = "expected a string"; + else + err = sway_bindsym (combination, action->value.string.str); + } + if (err) + print_warning ("configuration: key `%s': %s", combination, err); + } +} + // --- Signals ----------------------------------------------------------------- static int g_signal_pipe[2]; ///< A pipe used to signal... signals @@ -3079,15 +3223,16 @@ main (int argc, char *argv[]) { 'd', "debug", NULL, 0, "run in debug mode" }, { 'h', "help", NULL, 0, "display this help and exit" }, { 'V', "version", NULL, 0, "output version information and exit" }, - { '3', "i3bar", NULL, 0, "print output for i3bar/sway-bar instead" }, + { '3', "i3bar", NULL, 0, "print output for i3-bar/swaybar instead" }, + { 's', "bind-sway", NULL, 0, "import bindings over swaymsg" }, { 'w', "write-default-cfg", "FILENAME", OPT_OPTIONAL_ARG | OPT_LONG_ONLY, "write a default configuration file and exit" }, { 0, NULL, NULL, 0, NULL } }; - struct opt_handler oh = - opt_handler_make (argc, argv, opts, NULL, "Set root window name."); + struct opt_handler oh = opt_handler_make (argc, argv, opts, "[ACTION...]", + "Set root window name."); bool i3bar = false; int c; @@ -3106,6 +3251,9 @@ main (int argc, char *argv[]) case '3': i3bar = true; break; + case 's': + sway_forward_bindings (); + exit (EXIT_SUCCESS); case 'w': { // app_context_init() has side-effects. -- cgit v1.2.3-54-g00ecf