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',