]> git.mdlowis.com Git - proto/labwc.git/commitdiff
config: add `<core><promptCommand>`
authorJohan Malm <jgm323@gmail.com>
Mon, 22 Sep 2025 17:32:42 +0000 (18:32 +0100)
committerJohan Malm <johanmalm@users.noreply.github.com>
Wed, 24 Sep 2025 19:13:51 +0000 (20:13 +0100)
...to enable configuration of the action prompt command.

Also set some better defaults for labnag.

The new default 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

...where the conversion specifiers are defined as follows:

    %m: the `<prompt>` message option
    %n: _("No")
    %y: _("Yes")
    %b: osd.bg.color
    %t: osd.label.text.color

This config options also enables the use of a different dialog client, for
example like this:

    <core>
      <promptCommand>zenity --question --text="%m"</promptCommand>
    </core>

docs/rc.xml.all
include/action-prompt-command.h [new file with mode: 0644]
include/action.h
include/common/buf.h
include/config/rcxml.h
src/action-prompt-command.c [new file with mode: 0644]
src/action.c
src/common/buf.c
src/config/rcxml.c
src/meson.build

index 12d0c761c86b1b2166d32af5fc0cf9f2c0ff4df2..ed80fdf0739e924df12c7687c1cdaa985bf1d19e 100644 (file)
     <reuseOutputMode>no</reuseOutputMode>
     <xwaylandPersistence>no</xwaylandPersistence>
     <primarySelection>yes</primarySelection>
+    <!--
+      # See labwc-config(5) for details
+      <promptCommand></promptCommand>
+    -->
   </core>
 
   <placement>
diff --git a/include/action-prompt-command.h b/include/action-prompt-command.h
new file mode 100644 (file)
index 0000000..219896b
--- /dev/null
@@ -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 */
index cb88d0d599f27d207278cea5090d0cafc865651f..31ecfb6eba8d45fb1c7dc03ef990d7c651bde138 100644 (file)
@@ -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);
 
index a75c114406da9796b3bcfba8d0f07138cfabfe9b..857b6c48502a1bc85f7b30f1d8141df5b938916d 100644 (file)
@@ -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
index 89e215c0ea1cc527a4e6cd6dbf934501e732448d..e3b7bf0610b7af50496cebd481b58895b3ea0dff 100644 (file)
@@ -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 (file)
index 0000000..65bd0b4
--- /dev/null
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define _POSIX_C_SOURCE 200809L
+#include "action-prompt-command.h"
+#include <stdio.h>
+#include <wlr/util/log.h>
+#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);
+               }
+       }
+}
index ab9d3e1818e4be7990829a509653e99cf20b71f0..62ab2b2dfce98a19b81f99b11b3e10acc819266d 100644 (file)
@@ -10,6 +10,7 @@
 #include <wlr/types/wlr_scene.h>
 #include <wlr/util/log.h>
 #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
index c98fc97fccb3b4587c596ef8b1253286d7d8cc66..bd8e82d0f03e4b79467496b62a508ee1386c91aa 100644 (file)
@@ -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)
 {
index aeeecc20048ac060b60ec67d027fbafbe2eda383..162f3f1d1c95e7fbc24223f93dafc4470ded0b6b 100644 (file)
@@ -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);
index 330b5daf5f41d7b5a7b31f390df4596976994f80..dc760f0ccac08deed86ac3dc664aa08a9af9f43d 100644 (file)
@@ -1,5 +1,6 @@
 labwc_sources = files(
   'action.c',
+  'action-prompt-command.c',
   'buffer.c',
   'debug.c',
   'desktop.c',