diff --git a/clients/labnag.c b/clients/labnag.c index 20fb1dbe..bea25dd1 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -1370,7 +1370,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"detailed-button", required_argument, NULL, 'L'}, {"message", required_argument, NULL, 'm'}, {"output", required_argument, NULL, 'o'}, - {"timeout", no_argument, NULL, 't'}, + {"timeout", required_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"background", required_argument, NULL, TO_COLOR_BACKGROUND}, diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 7ddf2d5b..52cd9638 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -179,6 +179,7 @@ this is for compatibility with Openbox. no no yes + [see details below] ``` @@ -265,6 +266,53 @@ this is for compatibility with Openbox. up/down) in Chromium and electron based clients without inadvertantly pasting the primary clipboard. Default is yes. +** + Set command to be invoked for an action prompt (**) + + The following conversion specifiers are supported: + - *%m*: the ** message option + - *%n*: "No" (in local language if translation is available) + - *%y*: "Yes" (in local language if translation is available) + - *%b*: osd.bg.color + - *%t*: osd.label.text.color + + The default prompt command is: + + ``` + labnag \\ + --message '%m' \\ + --button-dismiss '%n' \\ + --button-dismiss '%y' \\ + --background '%b' \\ + --text '%t' \\ + --border '%t' \\ + --border-bottom '%t' \\ + --button-background '%b' \\ + --button-text '%t' \\ + --border-bottom-size 1 \\ + --button-border-size 3 \\ + --timeout 0 + ``` + + Example 1: The prompt can be configured to use a different dialog client + + ``` + + zenity --question --text="%m" + + ``` + + Example 2: A more complex zenity command could be used: + + ``` + zenity \\ + --question \\ + --title="" \\ + --text="%m" \\ + --ok-label="%y" \\ + --cancel-label="%n" + ``` + ## PLACEMENT ``` diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 12d0c761..ed80fdf0 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -19,6 +19,10 @@ no no yes + diff --git a/include/action-prompt-command.h b/include/action-prompt-command.h new file mode 100644 index 00000000..219896b9 --- /dev/null +++ b/include/action-prompt-command.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_ACTION_PROMPT_COMMAND_H +#define LABWC_ACTION_PROMPT_COMMAND_H + +struct buf; +struct action; +struct theme; + +void action_prompt_command(struct buf *buf, const char *format, + struct action *action, struct theme *theme); + +#endif /* LABWC_ACTION_PROMPT_COMMAND_H */ diff --git a/include/action.h b/include/action.h index cb88d0d5..31ecfb6e 100644 --- a/include/action.h +++ b/include/action.h @@ -23,6 +23,8 @@ struct action { struct action *action_create(const char *action_name); +const char *action_get_str(struct action *action, const char *key, + const char *default_value); bool action_is_valid(struct action *action); bool action_is_show_menu(struct action *action); diff --git a/include/common/buf.h b/include/common/buf.h index a75c1144..857b6c48 100644 --- a/include/common/buf.h +++ b/include/common/buf.h @@ -50,6 +50,17 @@ void buf_expand_shell_variables(struct buf *s); */ void buf_add_fmt(struct buf *s, const char *fmt, ...); +/** + * buf_add_hex_color - add rgb color as hex string to C string buffer + * @s: buffer + * @color: rgb color to be added + * + * For example: + * - With the input 'red' (defined as red[4] = { 1.0f, 0.0f, 0.0f, 1.0f}) the + * string "#ff0000ff" will be written to the buffer. + */ +void buf_add_hex_color(struct buf *s, float color[4]); + /** * buf_add - add data to C string buffer * @s: buffer diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 89e215c0..e3b7bf06 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -74,6 +74,8 @@ struct rcxml { enum lab_placement_policy placement_policy; bool xwayland_persistence; bool primary_selection; + char *prompt_command; + int placement_cascade_offset_x; int placement_cascade_offset_y; diff --git a/src/action-prompt-command.c b/src/action-prompt-command.c new file mode 100644 index 00000000..65bd0b49 --- /dev/null +++ b/src/action-prompt-command.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _POSIX_C_SOURCE 200809L +#include "action-prompt-command.h" +#include +#include +#include "action.h" +#include "common/buf.h" +#include "labwc.h" /* for gettext */ +#include "theme.h" + +enum { + LAB_PROMPT_NONE = 0, + LAB_PROMPT_MESSAGE, + LAB_PROMPT_NO, + LAB_PROMPT_YES, + LAB_PROMPT_BG_COL, + LAB_PROMPT_TEXT_COL, + + LAB_PROMPT_COUNT +}; + +typedef void field_conversion_type(struct buf *buf, struct action *action, + struct theme *theme); + +struct field_converter { + const char fmt_char; + field_conversion_type *fn; +}; + +/* %m */ +static void +set_message(struct buf *buf, struct action *action, struct theme *theme) +{ + buf_add(buf, action_get_str(action, "message.prompt", "Choose wisely")); +} + +/* %n */ +static void +set_no(struct buf *buf, struct action *action, struct theme *theme) +{ + buf_add(buf, _("No")); +} + +/* %y */ +static void +set_yes(struct buf *buf, struct action *action, struct theme *theme) +{ + buf_add(buf, _("Yes")); +} + +/* %b */ +static void +set_bg_col(struct buf *buf, struct action *action, struct theme *theme) +{ + buf_add_hex_color(buf, theme->osd_bg_color); +} + +/* %t */ +static void +set_text_col(struct buf *buf, struct action *action, struct theme *theme) +{ + buf_add_hex_color(buf, theme->osd_label_text_color); +} + +static const struct field_converter field_converter[LAB_PROMPT_COUNT] = { + [LAB_PROMPT_MESSAGE] = { 'm', set_message }, + [LAB_PROMPT_NO] = { 'n', set_no }, + [LAB_PROMPT_YES] = { 'y', set_yes }, + [LAB_PROMPT_BG_COL] = { 'b', set_bg_col }, + [LAB_PROMPT_TEXT_COL] = { 't', set_text_col }, +}; + +void +action_prompt_command(struct buf *buf, const char *format, + struct action *action, struct theme *theme) +{ + if (!format) { + wlr_log(WLR_ERROR, "missing format"); + return; + } + + for (const char *p = format; *p; p++) { + /* + * If we're not on a conversion specifier (like %m) then just + * keep adding it to the buffer + */ + if (*p != '%') { + buf_add_char(buf, *p); + continue; + } + + /* Process the %* conversion specifier */ + ++p; + + bool found = false; + for (unsigned char i = 0; i < LAB_PROMPT_COUNT; i++) { + if (*p == field_converter[i].fmt_char) { + field_converter[i].fn(buf, action, theme); + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_ERROR, + "invalid prompt command conversion specifier '%c'", *p); + } + } +} diff --git a/src/action.c b/src/action.c index ab9d3e18..62ab2b2d 100644 --- a/src/action.c +++ b/src/action.c @@ -10,6 +10,7 @@ #include #include #include "action-prompt-codes.h" +#include "action-prompt-command.h" #include "common/buf.h" #include "common/macros.h" #include "common/list.h" @@ -281,7 +282,7 @@ action_get_arg(struct action *action, const char *key, enum action_arg_type type return NULL; } -static const char * +const char * action_get_str(struct action *action, const char *key, const char *default_value) { struct action_arg_str *arg = action_get_arg(action, key, LAB_ACTION_ARG_STR); @@ -833,12 +834,13 @@ handle_view_destroy(struct wl_listener *listener, void *data) 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"), - _("No"), _("Yes")); + struct buf command = BUF_INIT; + action_prompt_command(&command, rc.prompt_command, action, rc.theme); + + wlr_log(WLR_INFO, "prompt command: '%s'", command.data); int pipe_fd; - pid_t prompt_pid = spawn_piped(command, &pipe_fd); + pid_t prompt_pid = spawn_piped(command.data, &pipe_fd); if (prompt_pid < 0) { wlr_log(WLR_ERROR, "Failed to create action prompt"); goto cleanup; @@ -862,7 +864,7 @@ action_prompt_create(struct view *view, struct server *server, struct action *ac wl_list_insert(&prompts, &prompt->link); cleanup: - free(command); + buf_reset(&command); } bool diff --git a/src/common/buf.c b/src/common/buf.c index c98fc97f..bd8e82d0 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -128,6 +128,30 @@ buf_add_fmt(struct buf *s, const char *fmt, ...) s->data[s->len] = 0; } +void +buf_add_hex_color(struct buf *s, float color[4]) +{ + /* + * In theme.c parse_hexstr() colors are pre-multiplied (by alpha) as + * expected by wlr_scene(). We therefore need to reverse that here. + * + * For details, see https://github.com/labwc/labwc/pull/1685 + */ + float alpha = color[3]; + + /* Avoid division by zero */ + if (alpha == 0.0f) { + buf_add(s, "#00000000"); + return; + } + + buf_add_fmt(s, "#%02x%02x%02x%02x", + (int)(color[0] / alpha * 255), + (int)(color[1] / alpha * 255), + (int)(color[2] / alpha * 255), + (int)(alpha * 255)); +} + void buf_add(struct buf *s, const char *data) { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index aeeecc20..162f3f1d 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1102,6 +1102,10 @@ entry(xmlNode *node, char *nodename, char *content) set_bool(content, &rc.xwayland_persistence); } else if (!strcasecmp(nodename, "primarySelection.core")) { set_bool(content, &rc.primary_selection); + + } else if (!strcasecmp(nodename, "promptCommand.core")) { + xstrdup_replace(rc.prompt_command, content); + } else if (!strcmp(nodename, "policy.placement")) { enum lab_placement_policy policy = view_placement_parse(content); if (policy != LAB_PLACE_INVALID) { @@ -1624,6 +1628,22 @@ post_processing(void) load_default_mouse_bindings(); } + if (!rc.prompt_command) { + rc.prompt_command = + xstrdup("labnag " + "--message '%m' " + "--button-dismiss '%n' " + "--button-dismiss '%y' " + "--background '%b' " + "--text '%t' " + "--border '%t' " + "--border-bottom '%t' " + "--button-background '%b' " + "--button-text '%t' " + "--border-bottom-size 1 " + "--button-border-size 3 " + "--timeout 0"); + } if (!rc.fallback_app_icon_name) { rc.fallback_app_icon_name = xstrdup("labwc"); } @@ -1886,6 +1906,7 @@ rcxml_finish(void) zfree(rc.font_menuheader.name); zfree(rc.font_menuitem.name); zfree(rc.font_osd.name); + zfree(rc.prompt_command); zfree(rc.theme_name); zfree(rc.icon_theme_name); zfree(rc.fallback_app_icon_name); diff --git a/src/meson.build b/src/meson.build index 330b5daf..dc760f0c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ labwc_sources = files( 'action.c', + 'action-prompt-command.c', 'buffer.c', 'debug.c', 'desktop.c',