action: add support for <prompt> in 'If' actions

...and allow If Action without activator view.

For example:

    <action name="If">
      <prompt message="Toggle maximize?"/>
      <then>
        <action name="ToggleMaximize" />
      </then>
    </action>

Also revert the change in b9c84f9 that <else> branch is always taken when
no window is focused.

Co-Authored-by: johanmalm
Co-Authored-by: tokyo4j
This commit is contained in:
Consolatis 2024-12-20 10:14:32 +01:00 committed by Johan Malm
parent 32e308b5d5
commit fba73a0036
3 changed files with 135 additions and 14 deletions

View file

@ -3,6 +3,7 @@
#define LABWC_ACTION_H
#include <stdbool.h>
#include <sys/types.h>
#include <wayland-util.h>
struct view;
@ -47,6 +48,8 @@ bool actions_contain_toggle_keybinds(struct wl_list *action_list);
void actions_run(struct view *activator, struct server *server,
struct wl_list *actions, struct cursor_context *ctx);
bool action_check_prompt_result(pid_t pid, int exit_code);
void action_free(struct action *action);
void action_list_free(struct wl_list *action_list);

View file

@ -493,6 +493,11 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
break;
case ACTION_TYPE_IF:
if (!strcmp(argument, "message.prompt")) {
action_arg_add_str(action, "message.prompt", content);
}
goto cleanup;
}
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
@ -775,16 +780,115 @@ view_for_action(struct view *activator, struct server *server,
}
}
struct action_prompt {
/* Set when created */
struct server *server;
struct action *action;
struct view *view;
/* Set when executed */
pid_t pid;
struct {
struct wl_listener destroy;
} on_view;
struct wl_list link;
};
static struct wl_list prompts = WL_LIST_INIT(&prompts);
static void
action_prompt_destroy(struct action_prompt *prompt)
{
wl_list_remove(&prompt->on_view.destroy.link);
wl_list_remove(&prompt->link);
free(prompt);
}
static void
handle_view_destroy(struct wl_listener *listener, void *data)
{
struct action_prompt *prompt = wl_container_of(listener, prompt, on_view.destroy);
wl_list_remove(&prompt->on_view.destroy.link);
wl_list_init(&prompt->on_view.destroy.link);
prompt->view = NULL;
}
static void
action_prompt_create(struct view *view, struct server *server, struct action *action)
{
char *command = strdup_printf("labnag -m \"%s\" -Z \"%s\" : -Z \"%s\" :",
action_get_str(action, "message.prompt", "Choose wisely"),
_("Yes"), _("No"));
int pipe_fd;
pid_t prompt_pid = spawn_piped(command, &pipe_fd);
if (prompt_pid < 0) {
wlr_log(WLR_ERROR, "Failed to create action prompt");
goto cleanup;
}
/* FIXME: closing stdout might confuse clients */
close(pipe_fd);
struct action_prompt *prompt = znew(*prompt);
prompt->server = server;
prompt->action = action;
prompt->view = view;
prompt->pid = prompt_pid;
if (view) {
prompt->on_view.destroy.notify = handle_view_destroy;
wl_signal_add(&view->events.destroy, &prompt->on_view.destroy);
} else {
/* Allows removing during destroy */
wl_list_init(&prompt->on_view.destroy.link);
}
wl_list_insert(&prompts, &prompt->link);
cleanup:
free(command);
}
bool
action_check_prompt_result(pid_t pid, int exit_code)
{
struct action_prompt *prompt, *tmp;
wl_list_for_each_safe(prompt, tmp, &prompts, link) {
if (prompt->pid != pid) {
continue;
}
wlr_log(WLR_INFO, "Found pending prompt for exit code %d", exit_code);
struct wl_list *actions = NULL;
if (exit_code == 0) {
wlr_log(WLR_INFO, "Selected the 'then' branch");
actions = action_get_actionlist(prompt->action, "then");
} else {
wlr_log(WLR_INFO, "Selected the 'else' branch");
actions = action_get_actionlist(prompt->action, "else");
}
if (actions) {
wlr_log(WLR_INFO, "Running actions");
actions_run(prompt->view, prompt->server,
actions, /*cursor_ctx*/ NULL);
} else {
wlr_log(WLR_INFO, "No actions for selected branch");
}
action_prompt_destroy(prompt);
return true;
}
return false;
}
static bool
match_queries(struct view *view, struct action *action)
{
assert(view);
struct wl_list *queries = action_get_querylist(action, "query");
if (!queries) {
return true;
}
if (!view) {
return false;
}
/* All queries are OR'ed */
struct view_query *query;
@ -1205,14 +1309,23 @@ run_action(struct view *view, struct server *server, struct action *action,
break;
}
case ACTION_TYPE_IF: {
struct wl_list *actions;
if (match_queries(view, action)) {
actions = action_get_actionlist(action, "then");
} else {
actions = action_get_actionlist(action, "else");
}
if (actions) {
actions_run(view, server, actions, ctx);
/* At least one of the queries was matched or there was no query */
if (action_get_str(action, "message.prompt", NULL)) {
/*
* We delay the selection and execution of the
* branch until we get a response from the user.
*/
action_prompt_create(view, server, action);
} else if (view) {
struct wl_list *actions;
if (match_queries(view, action)) {
actions = action_get_actionlist(action, "then");
} else {
actions = action_get_actionlist(action, "else");
}
if (actions) {
actions_run(view, server, actions, ctx);
}
}
break;
}

View file

@ -46,6 +46,7 @@
#endif
#include "drm-lease-v1-protocol.h"
#include "action.h"
#include "common/macros.h"
#include "common/scaled-scene-buffer.h"
#include "config/rcxml.h"
@ -160,9 +161,11 @@ handle_sigchld(int signal, void *data)
const char *signame;
switch (info.si_code) {
case CLD_EXITED:
wlr_log(info.si_status == 0 ? WLR_DEBUG : WLR_ERROR,
"spawned child %ld exited with %d",
(long)info.si_pid, info.si_status);
if (!action_check_prompt_result(info.si_pid, info.si_status)) {
wlr_log(info.si_status == 0 ? WLR_DEBUG : WLR_ERROR,
"spawned child %ld exited with %d",
(long)info.si_pid, info.si_status);
}
break;
case CLD_KILLED:
case CLD_DUMPED:
@ -171,6 +174,8 @@ handle_sigchld(int signal, void *data)
"spawned child %ld terminated with signal %d (%s)",
(long)info.si_pid, info.si_status,
signame ? signame : "unknown");
/* Allow cleanup of killed prompt */
action_check_prompt_result(info.si_pid, -info.si_status);
break;
default:
wlr_log(WLR_ERROR,