Compare commits

...

3 commits

Author SHA1 Message Date
Johan Malm
5fdebedcd9 labwc-config(5): document <promptCommand>
Some checks failed
labwc.github.io / notify (push) Has been cancelled
2025-09-24 20:13:51 +01:00
Johan Malm
5765586636 config: add <core><promptCommand>
...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>
2025-09-24 20:13:51 +01:00
Johan Malm
7028e65154 labnag: fix segfault caused by providing --timeout as long option 2025-09-24 20:13:51 +01:00
12 changed files with 242 additions and 7 deletions

View file

@ -1370,7 +1370,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag,
{"detailed-button", required_argument, NULL, 'L'}, {"detailed-button", required_argument, NULL, 'L'},
{"message", required_argument, NULL, 'm'}, {"message", required_argument, NULL, 'm'},
{"output", required_argument, NULL, 'o'}, {"output", required_argument, NULL, 'o'},
{"timeout", no_argument, NULL, 't'}, {"timeout", required_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{"background", required_argument, NULL, TO_COLOR_BACKGROUND}, {"background", required_argument, NULL, TO_COLOR_BACKGROUND},

View file

@ -179,6 +179,7 @@ this is for compatibility with Openbox.
<reuseOutputMode>no</reuseOutputMode> <reuseOutputMode>no</reuseOutputMode>
<xwaylandPersistence>no</xwaylandPersistence> <xwaylandPersistence>no</xwaylandPersistence>
<primarySelection>yes</primarySelection> <primarySelection>yes</primarySelection>
<promptCommand>[see details below]</promptCommand>
</core> </core>
``` ```
@ -265,6 +266,53 @@ this is for compatibility with Openbox.
up/down) in Chromium and electron based clients without inadvertantly up/down) in Chromium and electron based clients without inadvertantly
pasting the primary clipboard. Default is yes. pasting the primary clipboard. Default is yes.
*<core><promptCommand>*
Set command to be invoked for an action prompt (*<action><prompt>*)
The following conversion specifiers are supported:
- *%m*: the *<prompt>* 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
```
<core>
<promptCommand>zenity --question --text="%m"</promptCommand>
</core>
```
Example 2: A more complex zenity command could be used:
```
zenity \\
--question \\
--title="" \\
--text="%m" \\
--ok-label="%y" \\
--cancel-label="%n"
```
## PLACEMENT ## PLACEMENT
``` ```

View file

@ -19,6 +19,10 @@
<reuseOutputMode>no</reuseOutputMode> <reuseOutputMode>no</reuseOutputMode>
<xwaylandPersistence>no</xwaylandPersistence> <xwaylandPersistence>no</xwaylandPersistence>
<primarySelection>yes</primarySelection> <primarySelection>yes</primarySelection>
<!--
# See labwc-config(5) for details
<promptCommand></promptCommand>
-->
</core> </core>
<placement> <placement>

View file

@ -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 */

View file

@ -23,6 +23,8 @@ struct action {
struct action *action_create(const char *action_name); 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_valid(struct action *action);
bool action_is_show_menu(struct action *action); bool action_is_show_menu(struct action *action);

View file

@ -50,6 +50,17 @@ void buf_expand_shell_variables(struct buf *s);
*/ */
void buf_add_fmt(struct buf *s, const char *fmt, ...); 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 * buf_add - add data to C string buffer
* @s: buffer * @s: buffer

View file

@ -74,6 +74,8 @@ struct rcxml {
enum lab_placement_policy placement_policy; enum lab_placement_policy placement_policy;
bool xwayland_persistence; bool xwayland_persistence;
bool primary_selection; bool primary_selection;
char *prompt_command;
int placement_cascade_offset_x; int placement_cascade_offset_x;
int placement_cascade_offset_y; int placement_cascade_offset_y;

108
src/action-prompt-command.c Normal file
View file

@ -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);
}
}
}

View file

@ -10,6 +10,7 @@
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "action-prompt-codes.h" #include "action-prompt-codes.h"
#include "action-prompt-command.h"
#include "common/buf.h" #include "common/buf.h"
#include "common/macros.h" #include "common/macros.h"
#include "common/list.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; return NULL;
} }
static const char * const char *
action_get_str(struct action *action, const char *key, const char *default_value) 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); 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 static void
action_prompt_create(struct view *view, struct server *server, struct action *action) action_prompt_create(struct view *view, struct server *server, struct action *action)
{ {
char *command = strdup_printf("labnag -m \"%s\" -Z \"%s\" -Z \"%s\"", struct buf command = BUF_INIT;
action_get_str(action, "message.prompt", "Choose wisely"), action_prompt_command(&command, rc.prompt_command, action, rc.theme);
_("No"), _("Yes"));
wlr_log(WLR_INFO, "prompt command: '%s'", command.data);
int pipe_fd; 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) { if (prompt_pid < 0) {
wlr_log(WLR_ERROR, "Failed to create action prompt"); wlr_log(WLR_ERROR, "Failed to create action prompt");
goto cleanup; goto cleanup;
@ -862,7 +864,7 @@ action_prompt_create(struct view *view, struct server *server, struct action *ac
wl_list_insert(&prompts, &prompt->link); wl_list_insert(&prompts, &prompt->link);
cleanup: cleanup:
free(command); buf_reset(&command);
} }
bool bool

View file

@ -128,6 +128,30 @@ buf_add_fmt(struct buf *s, const char *fmt, ...)
s->data[s->len] = 0; 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 void
buf_add(struct buf *s, const char *data) buf_add(struct buf *s, const char *data)
{ {

View file

@ -1102,6 +1102,10 @@ entry(xmlNode *node, char *nodename, char *content)
set_bool(content, &rc.xwayland_persistence); set_bool(content, &rc.xwayland_persistence);
} else if (!strcasecmp(nodename, "primarySelection.core")) { } else if (!strcasecmp(nodename, "primarySelection.core")) {
set_bool(content, &rc.primary_selection); set_bool(content, &rc.primary_selection);
} else if (!strcasecmp(nodename, "promptCommand.core")) {
xstrdup_replace(rc.prompt_command, content);
} else if (!strcmp(nodename, "policy.placement")) { } else if (!strcmp(nodename, "policy.placement")) {
enum lab_placement_policy policy = view_placement_parse(content); enum lab_placement_policy policy = view_placement_parse(content);
if (policy != LAB_PLACE_INVALID) { if (policy != LAB_PLACE_INVALID) {
@ -1624,6 +1628,22 @@ post_processing(void)
load_default_mouse_bindings(); 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) { if (!rc.fallback_app_icon_name) {
rc.fallback_app_icon_name = xstrdup("labwc"); rc.fallback_app_icon_name = xstrdup("labwc");
} }
@ -1886,6 +1906,7 @@ rcxml_finish(void)
zfree(rc.font_menuheader.name); zfree(rc.font_menuheader.name);
zfree(rc.font_menuitem.name); zfree(rc.font_menuitem.name);
zfree(rc.font_osd.name); zfree(rc.font_osd.name);
zfree(rc.prompt_command);
zfree(rc.theme_name); zfree(rc.theme_name);
zfree(rc.icon_theme_name); zfree(rc.icon_theme_name);
zfree(rc.fallback_app_icon_name); zfree(rc.fallback_app_icon_name);

View file

@ -1,5 +1,6 @@
labwc_sources = files( labwc_sources = files(
'action.c', 'action.c',
'action-prompt-command.c',
'buffer.c', 'buffer.c',
'debug.c', 'debug.c',
'desktop.c', 'desktop.c',