This commit is contained in:
elviosak 2026-06-09 03:40:11 +08:00 committed by GitHub
commit 37a72a1608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 233 additions and 11 deletions

View file

@ -180,6 +180,7 @@ this is for compatibility with Openbox.
<xwaylandPersistence>no</xwaylandPersistence>
<primarySelection>yes</primarySelection>
<promptCommand>[see details below]</promptCommand>
<errorCommand>[see details below]</errorCommand>
</core>
```
@ -316,6 +317,29 @@ this is for compatibility with Openbox.
--cancel-label="%n"
```
*<core><errorCommand>*
Set command to be invoked for displaying errors in the config files,
it is executed when errors are detected on startup and reconfigure.
The errors are sent to STDIN of the program, and a SIGTERM is sent to
it if the process is still running when a reconfigure is triggered.
The default error command is:
```
labnag \\
--message 'Config Error' \\
--button-dismiss 'Close' \\
--layer overlay \\
--timeout 0 \\
--detailed-message
```
Example using `zenity`:
```
<core>
<errorCommand>zenity --title='Config Error' --text-info</errorCommand>
</core>
```
## PLACEMENT
```

21
include/common/log.h Normal file
View file

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_LOG_H
#define LABWC_LOG_H
#include <wlr/util/log.h>
#include "common/buf.h"
struct buf *log_get_buf(void);
void log_set_error(enum wlr_log_importance importance);
void log_reset(void);
void nag_show(void);
void nag_show_callback(void *data);
#define nag_log(verb, fmt, ...) \
do { \
wlr_log(verb, fmt, ##__VA_ARGS__); \
log_set_error(verb); \
buf_add_fmt(log_get_buf(), fmt "\n", ##__VA_ARGS__); \
} while (0)
#endif /* LABWC_LOG_H */

View file

@ -22,6 +22,19 @@ void spawn_async_no_shell(char const *command);
*/
void spawn_sync_no_shell(char const *command);
/**
* spawn_piped_async_no_shell - execute asynchronously
* @command: command to be executed
* @pipe_fd_w: set to the write end of a pipe
* connected to stdin of the command
* Notes:
* The returned pid_t has to be waited for to
* not produce zombies and the pipe_fd_w has to
* be closed. spawn_piped_close() can be used
* to ensure both.
*/
pid_t spawn_piped_async_no_shell(const char *command, int *pipe_fd_w);
/**
* spawn_piped - execute asynchronously
* @command: command to be executed

View file

@ -87,6 +87,7 @@ struct rcxml {
bool xwayland_persistence;
bool primary_selection;
char *prompt_command;
char *error_command;
/* placement */
enum lab_placement_policy placement_policy;

View file

@ -13,6 +13,7 @@
#include "common/buf.h"
#include "common/macros.h"
#include "common/list.h"
#include "common/log.h"
#include "common/mem.h"
#include "common/parse-bool.h"
#include "common/spawn.h"
@ -347,7 +348,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
@ -360,7 +361,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "focused")) {
action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
@ -371,7 +372,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
@ -401,7 +402,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (!strcmp(argument, "direction")) {
enum view_axis axis = view_axis_parse(content);
if (axis == VIEW_AXIS_NONE || axis == VIEW_AXIS_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
} else {
action_arg_add_int(action, argument, axis);
@ -415,7 +416,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (mode != LAB_SSD_MODE_INVALID) {
action_arg_add_int(action, argument, mode);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
@ -430,7 +431,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ true, /*any*/ false);
if (edge == LAB_EDGE_NONE || edge == LAB_EDGE_CENTER) {
wlr_log(WLR_ERROR,
nag_log(WLR_ERROR,
"Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
} else {
@ -497,7 +498,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ false, /*any*/ false);
if (edge == LAB_EDGE_NONE) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
} else {
action_arg_add_int(action, argument, edge);
@ -521,7 +522,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_placement_policy policy =
view_placement_parse(content);
if (policy == LAB_PLACE_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
} else {
action_arg_add_int(action, argument, policy);
@ -542,7 +543,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
nag_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
action_names[action->type], argument);
cleanup:

67
src/common/log.c Normal file
View file

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "common/log.h"
#include "common/spawn.h"
#include <stdarg.h>
#include <sys/wait.h>
#include <unistd.h>
#include "common/buf.h"
#include "config/rcxml.h"
static struct buf log_buf = BUF_INIT;
static bool has_error = false;
static pid_t pid = 0;
struct buf *
log_get_buf(void)
{
return &log_buf;
}
void
log_set_error(enum wlr_log_importance importance)
{
if (!has_error && importance == WLR_ERROR) {
has_error = true;
}
}
void
log_reset(void)
{
has_error = false;
buf_reset(&log_buf);
if (pid > 0) {
if (!waitpid(pid, NULL, WNOHANG)) {
kill(pid, SIGTERM);
/* waitpid() is done in a generic SIGCHLD handler in src/server.c */
}
}
pid = 0;
}
void
nag_show(void)
{
if (!has_error) {
return;
}
int pipe_w;
pid = spawn_piped_async_no_shell(rc.error_command, &pipe_w);
if (pid > 0) {
ssize_t bytes = write(pipe_w, log_buf.data, log_buf.len);
if (bytes < 0) {
wlr_log(WLR_ERROR, "Failed to write errors to process: %s",
rc.error_command);
}
spawn_piped_close(pid, pipe_w);
} else if (pid < 0) {
wlr_log(WLR_ERROR, "Failed to launch process: %s", rc.error_command);
}
}
void
nag_show_callback(void *data)
{
nag_show();
}

View file

@ -8,6 +8,7 @@ labwc_sources += files(
'font.c',
'graphic-helpers.c',
'lab-scene-rect.c',
'log.c',
'match.c',
'mem.c',
'nodename.c',

View file

@ -155,6 +155,82 @@ spawn_primary_client(const char *command)
}
}
pid_t
spawn_piped_async_no_shell(const char *command, int *pipe_fd_w)
{
assert(command);
GError *err = NULL;
gchar **argv = NULL;
/* Use glib's shell-parse to mimic Openbox's behaviour */
g_shell_parse_argv((gchar *)command, NULL, &argv, &err);
if (err) {
g_message("%s", err->message);
g_error_free(err);
return -1;
}
int pipe_rw[2];
if (pipe(pipe_rw) != 0) {
wlr_log(WLR_ERROR, "unable to pipe()");
g_strfreev(argv);
return -1;
}
pid_t child = 0;
child = fork();
if (child < 0) {
wlr_log(WLR_ERROR, "unable to fork() child");
close(pipe_rw[0]);
close(pipe_rw[1]);
g_strfreev(argv);
return child;
}
if (child == 0) {
/* Child */
reset_signals_and_limits();
/*
* replace stdout and stderr with /dev/null
* and stdin with the read end of the pipe
*/
close(pipe_rw[1]);
dup2(pipe_rw[0], STDIN_FILENO);
close(pipe_rw[0]);
int dev_null = open("/dev/null", O_WRONLY);
if (dev_null < 0) {
wlr_log_errno(WLR_ERROR, "opening /dev/null failed");
/*
* Just close stdout and stderr and
* hope $command can deal with that.
*/
close(STDOUT_FILENO);
close(STDERR_FILENO);
} else {
dup2(dev_null, STDOUT_FILENO);
dup2(dev_null, STDERR_FILENO);
close(dev_null);
}
execvp(argv[0], argv);
_exit(1);
}
/* labwc */
close(pipe_rw[0]);
g_strfreev(argv);
/*
* Prevent leaking the write end of the pipe to further
* children forked during the lifetime of the descriptor.
*/
set_cloexec(pipe_rw[1]);
*pipe_fd_w = pipe_rw[1];
return child;
}
pid_t
spawn_piped(const char *command, int *pipe_fd)
{

View file

@ -15,6 +15,7 @@
#include "common/buf.h"
#include "common/dir.h"
#include "common/list.h"
#include "common/log.h"
#include "common/macros.h"
#include "common/mem.h"
#include "common/nodename.h"
@ -1167,6 +1168,8 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "promptCommand.core")) {
xstrdup_replace(rc.prompt_command, content);
} else if (!strcasecmp(nodename, "errorCommand.core")) {
xstrdup_replace(rc.error_command, content);
} else if (!strcmp(nodename, "policy.placement")) {
enum lab_placement_policy policy = view_placement_parse(content);
@ -1476,7 +1479,7 @@ rcxml_parse_xml(struct buf *b)
int options = 0;
xmlDoc *d = xmlReadMemory(b->data, b->len, NULL, NULL, options);
if (!d) {
wlr_log(WLR_ERROR, "error parsing config file");
nag_log(WLR_ERROR, "error parsing config file");
return;
}
xmlNode *root = xmlDocGetRootElement(d);
@ -1802,6 +1805,15 @@ post_processing(void)
"--layer overlay "
"--timeout 0");
}
if (!rc.error_command) {
rc.error_command =
xstrdup("labnag "
"--message 'Config Error' "
"--button-dismiss 'Close' "
"--layer overlay "
"--timeout 0 "
"--detailed-message");
}
if (!rc.fallback_app_icon_name) {
rc.fallback_app_icon_name = xstrdup("labwc");
}
@ -2030,7 +2042,7 @@ rcxml_read(const char *filename)
continue;
}
wlr_log(WLR_INFO, "read config file %s", path->string);
nag_log(WLR_INFO, "read config file %s", path->string);
rcxml_parse_xml(&b);
buf_reset(&b);
@ -2052,6 +2064,7 @@ rcxml_finish(void)
zfree(rc.font_menuitem.name);
zfree(rc.font_osd.name);
zfree(rc.prompt_command);
zfree(rc.error_command);
zfree(rc.theme_name);
zfree(rc.icon_theme_name);
zfree(rc.fallback_app_icon_name);

View file

@ -52,6 +52,7 @@
#endif
#include "action.h"
#include "common/log.h"
#include "common/macros.h"
#include "common/mem.h"
#include "common/scene-helpers.h"
@ -98,6 +99,7 @@ reload_config_and_theme(void)
scaled_buffer_invalidate_sharing();
rcxml_finish();
log_reset();
rcxml_read(rc.config_file);
theme_finish(rc.theme);
theme_init(rc.theme, rc.theme_name);
@ -119,6 +121,8 @@ reload_config_and_theme(void)
resize_indicator_reconfigure();
kde_server_decoration_update_default();
workspaces_reconfigure();
wl_event_loop_add_idle(server.wl_event_loop, nag_show_callback, NULL);
}
static int
@ -809,6 +813,7 @@ server_init(void)
#if HAVE_XWAYLAND
xwayland_server_init(server.compositor);
#endif
wl_event_loop_add_idle(server.wl_event_loop, nag_show_callback, NULL);
}
void