add config dialog

This commit is contained in:
elviosak 2026-05-09 09:33:42 -03:00
parent ff2f243eb1
commit c8889c7fb7
12 changed files with 230 additions and 59 deletions

View file

@ -50,6 +50,7 @@ enum lab_node_type {
LAB_NODE_CYCLE_OSD_ITEM, LAB_NODE_CYCLE_OSD_ITEM,
LAB_NODE_LAYER_SURFACE, LAB_NODE_LAYER_SURFACE,
LAB_NODE_UNMANAGED, LAB_NODE_UNMANAGED,
LAB_NODE_CONFIG_DIALOG,
LAB_NODE_ALL, LAB_NODE_ALL,
/* translated to LAB_NODE_CLIENT by get_cursor_context() */ /* translated to LAB_NODE_CLIENT by get_cursor_context() */

23
include/config/dialog.h Normal file
View file

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LAB_DIALOG_H
#define LAB_DIALOG_H
struct wlr_scene_node;
/**
* dialog_create_callback - creates the config dialog,
* used with wl_event_loop_add_idle.
*
* @data: Ignored.
*/
void dialog_create_callback(void *data);
/**
* dialog_destroy - destroys a dialog that is a child of
* server.dialog_tree, destroys all children if NULL is provided.
*
* @dialog: Dialog to be destroyed
*/
void dialog_destroy(struct wlr_scene_node *dialog);
#endif /* LAB_DIALOG_H */

View file

@ -17,6 +17,28 @@
/* max of one button of each type (no repeats) */ /* max of one button of each type (no repeats) */
#define TITLE_BUTTONS_MAX ((LAB_NODE_BUTTON_LAST + 1) - LAB_NODE_BUTTON_FIRST) #define TITLE_BUTTONS_MAX ((LAB_NODE_BUTTON_LAST + 1) - LAB_NODE_BUTTON_FIRST)
struct log_item {
char *text;
struct wl_list link; /* struct rcxml.error_logs */
};
/**
* Calls wlr_log(verb, fmt, ...) and also saves it in
* rc.error_logs for displaying it in a dialog later.
* Only sets rc.has_error when WLR_ERROR is used to log
* other relevant info like the config file.
*/
#define lab_wlr_log(verb, fmt, ...) \
do { \
wlr_log(verb, fmt, ##__VA_ARGS__); \
if (verb == WLR_ERROR) { \
rc.has_error = true; \
} \
struct log_item *log_item = znew(*log_item); \
log_item->text = strdup_printf(fmt, ##__VA_ARGS__); \
wl_list_append(&rc.error_logs, &log_item->link); \
} while (0)
enum adaptive_sync_mode { enum adaptive_sync_mode {
LAB_ADAPTIVE_SYNC_DISABLED, LAB_ADAPTIVE_SYNC_DISABLED,
LAB_ADAPTIVE_SYNC_ENABLED, LAB_ADAPTIVE_SYNC_ENABLED,
@ -213,6 +235,10 @@ struct rcxml {
float mag_scale; float mag_scale;
float mag_increment; float mag_increment;
bool mag_filter; bool mag_filter;
/* Error log */
bool has_error;
struct wl_list error_logs; /* struct log_item.link */
}; };
/* defined in main.c */ /* defined in main.c */

View file

@ -230,6 +230,9 @@ struct server {
/* Tree for all non-layer xdg/xwayland-shell surfaces */ /* Tree for all non-layer xdg/xwayland-shell surfaces */
struct wlr_scene_tree *workspace_tree; struct wlr_scene_tree *workspace_tree;
/* Tree for error dialog */
struct wlr_scene_tree *dialog_tree;
/* /*
* Popups need to be rendered above always-on-top views, so we reparent * Popups need to be rendered above always-on-top views, so we reparent
* them to this dedicated tree * them to this dedicated tree

View file

@ -318,7 +318,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
|| action->type == ACTION_TYPE_SNAP_TO_EDGE); || action->type == ACTION_TYPE_SNAP_TO_EDGE);
enum lab_edge edge = lab_edge_parse(content, tiled, /*any*/ false); enum lab_edge edge = lab_edge_parse(content, tiled, /*any*/ false);
if (edge == LAB_EDGE_NONE) { if (edge == LAB_EDGE_NONE) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} else { } else {
action_arg_add_int(action, argument, edge); action_arg_add_int(action, argument, edge);
@ -347,7 +347,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "current")) { } else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT); action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT);
} else { } else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} }
goto cleanup; goto cleanup;
@ -360,7 +360,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "focused")) { } else if (!strcasecmp(content, "focused")) {
action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED); action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED);
} else { } else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} }
goto cleanup; goto cleanup;
@ -371,7 +371,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
} else if (!strcasecmp(content, "current")) { } else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT); action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT);
} else { } else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} }
goto cleanup; goto cleanup;
@ -401,7 +401,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (!strcmp(argument, "direction")) { if (!strcmp(argument, "direction")) {
enum view_axis axis = view_axis_parse(content); enum view_axis axis = view_axis_parse(content);
if (axis == VIEW_AXIS_NONE || axis == VIEW_AXIS_INVALID) { if (axis == VIEW_AXIS_NONE || axis == VIEW_AXIS_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} else { } else {
action_arg_add_int(action, argument, axis); action_arg_add_int(action, argument, axis);
@ -415,7 +415,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
if (mode != LAB_SSD_MODE_INVALID) { if (mode != LAB_SSD_MODE_INVALID) {
action_arg_add_int(action, argument, mode); action_arg_add_int(action, argument, mode);
} else { } else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} }
goto cleanup; goto cleanup;
@ -430,7 +430,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_edge edge = lab_edge_parse(content, enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ true, /*any*/ false); /*tiled*/ true, /*any*/ false);
if (edge == LAB_EDGE_NONE || edge == LAB_EDGE_CENTER) { if (edge == LAB_EDGE_NONE || edge == LAB_EDGE_CENTER) {
wlr_log(WLR_ERROR, lab_wlr_log(WLR_ERROR,
"Invalid argument for action %s: '%s' (%s)", "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} else { } else {
@ -497,7 +497,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_edge edge = lab_edge_parse(content, enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ false, /*any*/ false); /*tiled*/ false, /*any*/ false);
if (edge == LAB_EDGE_NONE) { if (edge == LAB_EDGE_NONE) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} else { } else {
action_arg_add_int(action, argument, edge); action_arg_add_int(action, argument, edge);
@ -521,7 +521,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
enum lab_placement_policy policy = enum lab_placement_policy policy =
view_placement_parse(content); view_placement_parse(content);
if (policy == LAB_PLACE_INVALID) { if (policy == LAB_PLACE_INVALID) {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content); action_names[action->type], argument, content);
} else { } else {
action_arg_add_int(action, argument, policy); action_arg_add_int(action, argument, policy);
@ -542,7 +542,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup; goto cleanup;
} }
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'", lab_wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'",
action_names[action->type], argument); action_names[action->type], argument);
cleanup: cleanup:
@ -557,7 +557,7 @@ action_type_from_str(const char *action_name)
return i; return i;
} }
} }
wlr_log(WLR_ERROR, "Invalid action: %s", action_name); lab_wlr_log(WLR_ERROR, "Invalid action: %s", action_name);
return ACTION_TYPE_INVALID; return ACTION_TYPE_INVALID;
} }
@ -664,7 +664,7 @@ action_is_valid(struct action *action)
return true; return true;
} }
wlr_log(WLR_ERROR, "Missing required argument for %s: %s", lab_wlr_log(WLR_ERROR, "Missing required argument for %s: %s",
action_names[action->type], arg_name); action_names[action->type], arg_name);
return false; return false;
} }

88
src/config/dialog.c Normal file
View file

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "config/dialog.h"
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h>
#include "common/lab-scene-rect.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "labwc.h"
#include "node.h"
#include "output.h"
#include "scaled-buffer/scaled-font-buffer.h"
#include "theme.h"
static void
dialog_create(void)
{
struct theme *theme = rc.theme;
float *text_color = theme->osd_label_text_color;
float *bg_color = theme->osd_bg_color;
struct output *output = output_nearest_to_cursor();
struct wlr_box output_box = output_usable_area_in_layout_coords(output);
int border = theme->osd_border_width;
int padding = rc.font_osd.size / 2; /* used for padding and line gap */
int x = output_box.x + output_box.width / 4;
int y = output_box.y + output_box.height / 10;
int width = output_box.width * 0.5;
int height = output_box.height * 0.8;
int inner_w = width - 2 * border;
int inner_h = height - 2 * border;
int dx = border + padding;
int dy = border + padding;
struct wlr_scene_tree *dialog = lab_wlr_scene_tree_create(server.dialog_tree);
node_descriptor_create(&dialog->node, LAB_NODE_CONFIG_DIALOG, NULL, NULL);
wlr_scene_node_set_position(&dialog->node, x, y);
struct wlr_scene_rect *border_rect = lab_wlr_scene_rect_create(dialog, width, height,
theme->osd_border_color);
wlr_scene_node_set_position(&border_rect->node, 0, 0);
struct wlr_scene_tree *dialog_tree = lab_wlr_scene_tree_create(dialog);
wlr_scene_node_set_position(&dialog_tree->node, border, border);
struct wlr_scene_rect *dialog_rect = lab_wlr_scene_rect_create(dialog_tree,
inner_w, inner_h, bg_color);
wlr_scene_node_set_position(&dialog_rect->node, 0, 0);
struct scaled_font_buffer *font_buf = scaled_font_buffer_create(dialog_tree);
struct font title_font = rc.font_osd;
title_font.size *= 1.15;
scaled_font_buffer_update(font_buf, "Config log (Click to dismiss)", inner_w,
&title_font, text_color, bg_color);
wlr_scene_node_set_position(&font_buf->scene_buffer->node, dx, dy);
dy += font_buf->height + padding;
struct log_item *item;
wl_list_for_each(item, &rc.error_logs, link) {
font_buf = scaled_font_buffer_create(dialog_tree);
scaled_font_buffer_update(font_buf, item->text, inner_w, &rc.font_osd,
text_color, bg_color);
wlr_scene_node_set_position(&font_buf->scene_buffer->node, dx, dy);
dy += font_buf->height + padding;
}
wlr_scene_node_set_enabled(&server.dialog_tree->node, true);
}
void
dialog_create_callback(void *data)
{
dialog_create();
}
void
dialog_destroy(struct wlr_scene_node *dialog)
{
struct wlr_scene_node *child, *tmp;
wl_list_for_each_safe(child, tmp, &server.dialog_tree->children, link) {
if (!dialog) {
wlr_scene_node_destroy(child);
continue;
}
if (dialog == child) {
wlr_scene_node_destroy(child);
break;
}
}
}

View file

@ -1,4 +1,5 @@
labwc_sources += files( labwc_sources += files(
'dialog.c',
'rcxml.c', 'rcxml.c',
'keybind.c', 'keybind.c',
'session.c', 'session.c',

View file

@ -23,6 +23,7 @@
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "common/xml.h" #include "common/xml.h"
#include "config/default-bindings.h" #include "config/default-bindings.h"
#include "config/dialog.h"
#include "config/keybind.h" #include "config/keybind.h"
#include "config/libinput.h" #include "config/libinput.h"
#include "config/mousebind.h" #include "config/mousebind.h"
@ -168,7 +169,7 @@ fill_section(const char *content, enum lab_node_type *buttons, int *count,
#if HAVE_LIBSFDO #if HAVE_LIBSFDO
type = LAB_NODE_BUTTON_WINDOW_ICON; type = LAB_NODE_BUTTON_WINDOW_ICON;
#else #else
wlr_log(WLR_ERROR, "libsfdo is not linked. " lab_wlr_log(WLR_ERROR, "libsfdo is not linked. "
"Replacing 'icon' in titlebar layout with 'menu'."); "Replacing 'icon' in titlebar layout with 'menu'.");
type = LAB_NODE_BUTTON_WINDOW_MENU; type = LAB_NODE_BUTTON_WINDOW_MENU;
#endif #endif
@ -185,7 +186,7 @@ fill_section(const char *content, enum lab_node_type *buttons, int *count,
} else if (!strcmp(identifier, "desk")) { } else if (!strcmp(identifier, "desk")) {
type = LAB_NODE_BUTTON_OMNIPRESENT; type = LAB_NODE_BUTTON_OMNIPRESENT;
} else { } else {
wlr_log(WLR_ERROR, "invalid titleLayout identifier '%s'", lab_wlr_log(WLR_ERROR, "invalid titleLayout identifier '%s'",
identifier); identifier);
continue; continue;
} }
@ -194,7 +195,7 @@ fill_section(const char *content, enum lab_node_type *buttons, int *count,
/* We no longer need this check, but let's keep it just in case */ /* We no longer need this check, but let's keep it just in case */
if (*found_buttons & (1 << type)) { if (*found_buttons & (1 << type)) {
wlr_log(WLR_ERROR, "ignoring duplicated button type '%s'", lab_wlr_log(WLR_ERROR, "ignoring duplicated button type '%s'",
identifier); identifier);
continue; continue;
} }
@ -223,7 +224,7 @@ fill_title_layout(const char *content)
gchar **parts = g_strsplit(content, ":", -1); gchar **parts = g_strsplit(content, ":", -1);
if (g_strv_length(parts) != 2) { if (g_strv_length(parts) != 2) {
wlr_log(WLR_ERROR, "<titlebar><layout> must contain one colon"); lab_wlr_log(WLR_ERROR, "<titlebar><layout> must contain one colon");
goto err; goto err;
} }
@ -259,7 +260,7 @@ fill_usable_area_override(xmlNode *node)
} else if (!strcmp(key, "bottom")) { } else if (!strcmp(key, "bottom")) {
usable_area_override->margin.bottom = atoi(content); usable_area_override->margin.bottom = atoi(content);
} else { } else {
wlr_log(WLR_ERROR, "Unexpected data usable-area-override " lab_wlr_log(WLR_ERROR, "Unexpected data usable-area-override "
"parser: %s=\"%s\"", key, content); "parser: %s=\"%s\"", key, content);
} }
} }
@ -324,7 +325,7 @@ fill_window_rule(xmlNode *node)
} else if (!strcasecmp(content, "server")) { } else if (!strcasecmp(content, "server")) {
window_rule->icon_prefer_client = LAB_PROP_FALSE; window_rule->icon_prefer_client = LAB_PROP_FALSE;
} else { } else {
wlr_log(WLR_ERROR, lab_wlr_log(WLR_ERROR,
"Invalid value for window rule property 'iconPriority'"); "Invalid value for window rule property 'iconPriority'");
} }
} else if (!strcasecmp(key, "skipTaskbar")) { } else if (!strcasecmp(key, "skipTaskbar")) {
@ -409,7 +410,7 @@ fill_region(xmlNode *node)
xstrdup_replace(region->name, content); xstrdup_replace(region->name, content);
} else if (strstr("xywidtheight", key) } else if (strstr("xywidtheight", key)
&& !strchr(content, '%')) { && !strchr(content, '%')) {
wlr_log(WLR_ERROR, "Removing invalid region " lab_wlr_log(WLR_ERROR, "Removing invalid region "
"'%s': %s='%s' misses a trailing %%", "'%s': %s='%s' misses a trailing %%",
region->name, key, content); region->name, key, content);
wl_list_remove(&region->link); wl_list_remove(&region->link);
@ -425,7 +426,7 @@ fill_region(xmlNode *node)
} else if (!strcmp(key, "height")) { } else if (!strcmp(key, "height")) {
region->percentage.height = atoi(content); region->percentage.height = atoi(content);
} else { } else {
wlr_log(WLR_ERROR, "Unexpected data in region " lab_wlr_log(WLR_ERROR, "Unexpected data in region "
"parser: %s=\"%s\"", key, content); "parser: %s=\"%s\"", key, content);
} }
} }
@ -592,7 +593,7 @@ fill_keybind(xmlNode *node)
if (lab_xml_get_string(node, "key", keyname, sizeof(keyname))) { if (lab_xml_get_string(node, "key", keyname, sizeof(keyname))) {
keybind = keybind_create(keyname); keybind = keybind_create(keyname);
if (!keybind) { if (!keybind) {
wlr_log(WLR_ERROR, "Invalid keybind: %s", keyname); lab_wlr_log(WLR_ERROR, "Invalid keybind: %s", keyname);
} }
} }
if (!keybind) { if (!keybind) {
@ -672,7 +673,7 @@ fill_touch(xmlNode *node)
} else if (!strcasecmp(key, "mouseEmulation")) { } else if (!strcasecmp(key, "mouseEmulation")) {
set_bool(content, &touch_config->force_mouse_emulation); set_bool(content, &touch_config->force_mouse_emulation);
} else { } else {
wlr_log(WLR_ERROR, "Unexpected data in touch parser: %s=\"%s\"", lab_wlr_log(WLR_ERROR, "Unexpected data in touch parser: %s=\"%s\"",
key, content); key, content);
} }
} }
@ -688,14 +689,14 @@ fill_tablet_button_map(xmlNode *node)
if (lab_xml_get_string(node, "button", buf, sizeof(buf))) { if (lab_xml_get_string(node, "button", buf, sizeof(buf))) {
map_from = tablet_button_from_str(buf); map_from = tablet_button_from_str(buf);
} else { } else {
wlr_log(WLR_ERROR, "Invalid 'button' argument for tablet button mapping"); lab_wlr_log(WLR_ERROR, "Invalid 'button' argument for tablet button mapping");
return; return;
} }
if (lab_xml_get_string(node, "to", buf, sizeof(buf))) { if (lab_xml_get_string(node, "to", buf, sizeof(buf))) {
map_to = mousebind_button_from_str(buf, NULL); map_to = mousebind_button_from_str(buf, NULL);
} else { } else {
wlr_log(WLR_ERROR, "Invalid 'to' argument for tablet button mapping"); lab_wlr_log(WLR_ERROR, "Invalid 'to' argument for tablet button mapping");
return; return;
} }
@ -754,7 +755,7 @@ fill_libinput_category(xmlNode *node)
char *key, *content; char *key, *content;
LAB_XML_FOR_EACH(node, child, key, content) { LAB_XML_FOR_EACH(node, child, key, content) {
if (string_null_or_empty(content)) { if (string_null_or_empty(content)) {
wlr_log(WLR_ERROR, "Empty string is not allowed for " lab_wlr_log(WLR_ERROR, "Empty string is not allowed for "
"<libinput><device><%s>. Ignoring.", key); "<libinput><device><%s>. Ignoring.", key);
continue; continue;
} }
@ -801,7 +802,7 @@ fill_libinput_category(xmlNode *node)
category->tap_button_map = category->tap_button_map =
LIBINPUT_CONFIG_TAP_MAP_LMR; LIBINPUT_CONFIG_TAP_MAP_LMR;
} else { } else {
wlr_log(WLR_ERROR, "invalid tapButtonMap"); lab_wlr_log(WLR_ERROR, "invalid tapButtonMap");
} }
} else if (!strcasecmp(key, "tapAndDrag")) { } else if (!strcasecmp(key, "tapAndDrag")) {
int ret = parse_bool(content, -1); int ret = parse_bool(content, -1);
@ -846,7 +847,7 @@ fill_libinput_category(xmlNode *node)
: LIBINPUT_CONFIG_3FG_DRAG_DISABLED; : LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
} }
#else #else
wlr_log(WLR_ERROR, "<threeFingerDrag> is only" lab_wlr_log(WLR_ERROR, "<threeFingerDrag> is only"
" supported in libinput >= 1.28"); " supported in libinput >= 1.28");
#endif #endif
} else if (!strcasecmp(key, "accelProfile")) { } else if (!strcasecmp(key, "accelProfile")) {
@ -879,7 +880,7 @@ fill_libinput_category(xmlNode *node)
category->click_method = category->click_method =
LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
} else { } else {
wlr_log(WLR_ERROR, "invalid clickMethod"); lab_wlr_log(WLR_ERROR, "invalid clickMethod");
} }
} else if (!strcasecmp(key, "scrollMethod")) { } else if (!strcasecmp(key, "scrollMethod")) {
if (!strcasecmp(content, "none")) { if (!strcasecmp(content, "none")) {
@ -895,14 +896,14 @@ fill_libinput_category(xmlNode *node)
category->scroll_method = category->scroll_method =
LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
} else { } else {
wlr_log(WLR_ERROR, "invalid scrollMethod"); lab_wlr_log(WLR_ERROR, "invalid scrollMethod");
} }
} else if (!strcasecmp(key, "scrollButton")) { } else if (!strcasecmp(key, "scrollButton")) {
int button = atoi(content); int button = atoi(content);
if (button != 0) { if (button != 0) {
category->scroll_button = button; category->scroll_button = button;
} else { } else {
wlr_log(WLR_ERROR, "invalid scrollButton"); lab_wlr_log(WLR_ERROR, "invalid scrollButton");
} }
} else if (!strcasecmp(key, "sendEventsMode")) { } else if (!strcasecmp(key, "sendEventsMode")) {
category->send_events_mode = category->send_events_mode =
@ -918,7 +919,7 @@ fill_libinput_category(xmlNode *node)
mat[i] = strtof(elements[i], &end_str); mat[i] = strtof(elements[i], &end_str);
if (errno == ERANGE || *end_str != '\0' || i == 6 if (errno == ERANGE || *end_str != '\0' || i == 6
|| *elements[i] == '\0') { || *elements[i] == '\0') {
wlr_log(WLR_ERROR, "invalid calibration " lab_wlr_log(WLR_ERROR, "invalid calibration "
"matrix element %s (index %d), " "matrix element %s (index %d), "
"expect six floats", elements[i], i); "expect six floats", elements[i], i);
category->have_calibration_matrix = false; category->have_calibration_matrix = false;
@ -927,7 +928,7 @@ fill_libinput_category(xmlNode *node)
} }
} }
if (i != 6 && category->have_calibration_matrix) { if (i != 6 && category->have_calibration_matrix) {
wlr_log(WLR_ERROR, "wrong number of calibration " lab_wlr_log(WLR_ERROR, "wrong number of calibration "
"matrix elements, expected 6, got %d", i); "matrix elements, expected 6, got %d", i);
category->have_calibration_matrix = false; category->have_calibration_matrix = false;
} }
@ -1128,7 +1129,7 @@ entry(xmlNode *node, char *nodename, char *content)
return true; return true;
} else if (str_space_only(content)) { } else if (str_space_only(content)) {
wlr_log(WLR_ERROR, "Empty string is not allowed for %s. " lab_wlr_log(WLR_ERROR, "Empty string is not allowed for %s. "
"Ignoring.", nodename); "Ignoring.", nodename);
/* handle non-empty leaf nodes */ /* handle non-empty leaf nodes */
@ -1203,7 +1204,7 @@ entry(xmlNode *node, char *nodename, char *content)
if (doubleclick_time_parsed > 0) { if (doubleclick_time_parsed > 0) {
rc.doubleclick_time = doubleclick_time_parsed; rc.doubleclick_time = doubleclick_time_parsed;
} else { } else {
wlr_log(WLR_ERROR, "invalid doubleClickTime"); lab_wlr_log(WLR_ERROR, "invalid doubleClickTime");
} }
} else if (!strcasecmp(nodename, "scrollFactor.mouse")) { } else if (!strcasecmp(nodename, "scrollFactor.mouse")) {
/* This is deprecated. Show an error message in post_processing() */ /* This is deprecated. Show an error message in post_processing() */
@ -1235,7 +1236,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "range.snapping")) { } else if (!strcasecmp(nodename, "range.snapping")) {
rc.snap_edge_range_inner = atoi(content); rc.snap_edge_range_inner = atoi(content);
rc.snap_edge_range_outer = atoi(content); rc.snap_edge_range_outer = atoi(content);
wlr_log(WLR_ERROR, "<snapping><range> is deprecated. " lab_wlr_log(WLR_ERROR, "<snapping><range> is deprecated. "
"Use <snapping><range inner=\"\" outer=\"\"> instead."); "Use <snapping><range inner=\"\" outer=\"\"> instead.");
} else if (!strcasecmp(nodename, "inner.range.snapping")) { } else if (!strcasecmp(nodename, "inner.range.snapping")) {
rc.snap_edge_range_inner = atoi(content); rc.snap_edge_range_inner = atoi(content);
@ -1261,7 +1262,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "never")) { } else if (!strcasecmp(content, "never")) {
rc.snap_tiling_events_mode = LAB_TILING_EVENTS_NEVER; rc.snap_tiling_events_mode = LAB_TILING_EVENTS_NEVER;
} else { } else {
wlr_log(WLR_ERROR, "ignoring invalid value for notifyClient"); lab_wlr_log(WLR_ERROR, "ignoring invalid value for notifyClient");
} }
/* /*
@ -1279,7 +1280,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "thumbnail")) { } else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
} else { } else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': " lab_wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': "
"should be one of classic|thumbnail", content); "should be one of classic|thumbnail", content);
} }
} else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) {
@ -1290,7 +1291,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "focused")) { } else if (!strcasecmp(content, "focused")) {
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED; rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED;
} else { } else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': " lab_wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': "
"should be one of all|focused|cursor", content); "should be one of all|focused|cursor", content);
} }
} else if (!strcasecmp(nodename, "order.windowSwitcher")) { } else if (!strcasecmp(nodename, "order.windowSwitcher")) {
@ -1299,14 +1300,14 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "age")) { } else if (!strcasecmp(content, "age")) {
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE; rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE;
} else { } else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher order '%s': " lab_wlr_log(WLR_ERROR, "Invalid windowSwitcher order '%s': "
"should be one of focus|age", content); "should be one of focus|age", content);
} }
/* The following two are for backward compatibility only. */ /* The following two are for backward compatibility only. */
} else if (!strcasecmp(nodename, "show.windowSwitcher")) { } else if (!strcasecmp(nodename, "show.windowSwitcher")) {
set_bool(content, &rc.window_switcher.osd.show); set_bool(content, &rc.window_switcher.osd.show);
wlr_log(WLR_ERROR, "<windowSwitcher show=\"\" /> is deprecated." lab_wlr_log(WLR_ERROR, "<windowSwitcher show=\"\" /> is deprecated."
" Use <windowSwitcher><osd show=\"\" />"); " Use <windowSwitcher><osd show=\"\" />");
} else if (!strcasecmp(nodename, "style.windowSwitcher")) { } else if (!strcasecmp(nodename, "style.windowSwitcher")) {
if (!strcasecmp(content, "classic")) { if (!strcasecmp(content, "classic")) {
@ -1314,7 +1315,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "thumbnail")) { } else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
} }
wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated." lab_wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated."
" Use <windowSwitcher><osd style=\"\" />"); " Use <windowSwitcher><osd style=\"\" />");
} else if (!strcasecmp(nodename, "preview.windowSwitcher")) { } else if (!strcasecmp(nodename, "preview.windowSwitcher")) {
@ -1324,20 +1325,20 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) { } else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) {
int ret = parse_bool(content, -1); int ret = parse_bool(content, -1);
if (ret < 0) { if (ret < 0) {
wlr_log(WLR_ERROR, "Invalid value for <windowSwitcher" lab_wlr_log(WLR_ERROR, "Invalid value for <windowSwitcher"
" allWorkspaces=\"\">: '%s'", content); " allWorkspaces=\"\">: '%s'", content);
} else { } else {
rc.window_switcher.workspace_filter = ret ? rc.window_switcher.workspace_filter = ret ?
CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT; CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT;
} }
wlr_log(WLR_ERROR, "<windowSwitcher allWorkspaces=\"\" /> is deprecated." lab_wlr_log(WLR_ERROR, "<windowSwitcher allWorkspaces=\"\" /> is deprecated."
" Use <action name=\"NextWindow\" workspace=\"\"> instead."); " Use <action name=\"NextWindow\" workspace=\"\"> instead.");
} else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) {
set_bool(content, &rc.window_switcher.unshade); set_bool(content, &rc.window_switcher.unshade);
/* Remove this long term - just a friendly warning for now */ /* Remove this long term - just a friendly warning for now */
} else if (strstr(nodename, "windowswitcher.core")) { } else if (strstr(nodename, "windowswitcher.core")) {
wlr_log(WLR_ERROR, "<windowSwitcher> should not be child of <core>"); lab_wlr_log(WLR_ERROR, "<windowSwitcher> should not be child of <core>");
/* The following three are for backward compatibility only */ /* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "show.windowSwitcher.core")) { } else if (!strcasecmp(nodename, "show.windowSwitcher.core")) {
@ -1350,15 +1351,15 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following three are for backward compatibility only */ /* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "cycleViewOSD.core")) { } else if (!strcasecmp(nodename, "cycleViewOSD.core")) {
set_bool(content, &rc.window_switcher.osd.show); set_bool(content, &rc.window_switcher.osd.show);
wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated." lab_wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated."
" Use <windowSwitcher show=\"\" />"); " Use <windowSwitcher show=\"\" />");
} else if (!strcasecmp(nodename, "cycleViewPreview.core")) { } else if (!strcasecmp(nodename, "cycleViewPreview.core")) {
set_bool(content, &rc.window_switcher.preview); set_bool(content, &rc.window_switcher.preview);
wlr_log(WLR_ERROR, "<cycleViewPreview> is deprecated." lab_wlr_log(WLR_ERROR, "<cycleViewPreview> is deprecated."
" Use <windowSwitcher preview=\"\" />"); " Use <windowSwitcher preview=\"\" />");
} else if (!strcasecmp(nodename, "cycleViewOutlines.core")) { } else if (!strcasecmp(nodename, "cycleViewOutlines.core")) {
set_bool(content, &rc.window_switcher.outlines); set_bool(content, &rc.window_switcher.outlines);
wlr_log(WLR_ERROR, "<cycleViewOutlines> is deprecated." lab_wlr_log(WLR_ERROR, "<cycleViewOutlines> is deprecated."
" Use <windowSwitcher outlines=\"\" />"); " Use <windowSwitcher outlines=\"\" />");
} else if (!strcasecmp(nodename, "name.names.desktops")) { } else if (!strcasecmp(nodename, "name.names.desktops")) {
@ -1379,7 +1380,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(content, "Nonpixel")) { } else if (!strcasecmp(content, "Nonpixel")) {
rc.resize_indicator = LAB_RESIZE_INDICATOR_NON_PIXEL; rc.resize_indicator = LAB_RESIZE_INDICATOR_NON_PIXEL;
} else { } else {
wlr_log(WLR_ERROR, "Invalid value for <resize popupShow />"); lab_wlr_log(WLR_ERROR, "Invalid value for <resize popupShow />");
} }
} else if (!strcasecmp(nodename, "drawContents.resize")) { } else if (!strcasecmp(nodename, "drawContents.resize")) {
set_bool(content, &rc.resize_draw_contents); set_bool(content, &rc.resize_draw_contents);
@ -1435,7 +1436,7 @@ entry(xmlNode *node, char *nodename, char *content)
if (iface_id) { if (iface_id) {
rc.allowed_interfaces |= iface_id; rc.allowed_interfaces |= iface_id;
} else { } else {
wlr_log(WLR_ERROR, "invalid value for " lab_wlr_log(WLR_ERROR, "invalid value for "
"<privilegedInterfaces><allow>"); "<privilegedInterfaces><allow>");
} }
} }
@ -1464,7 +1465,7 @@ rcxml_parse_xml(struct buf *b)
int options = 0; int options = 0;
xmlDoc *d = xmlReadMemory(b->data, b->len, NULL, NULL, options); xmlDoc *d = xmlReadMemory(b->data, b->len, NULL, NULL, options);
if (!d) { if (!d) {
wlr_log(WLR_ERROR, "error parsing config file"); lab_wlr_log(WLR_ERROR, "error parsing config file");
return; return;
} }
xmlNode *root = xmlDocGetRootElement(d); xmlNode *root = xmlDocGetRootElement(d);
@ -1495,6 +1496,7 @@ rcxml_init(void)
wl_list_init(&rc.mousebinds); wl_list_init(&rc.mousebinds);
wl_list_init(&rc.libinput_categories); wl_list_init(&rc.libinput_categories);
wl_list_init(&rc.workspace_config.workspaces); wl_list_init(&rc.workspace_config.workspaces);
wl_list_init(&rc.error_logs);
wl_list_init(&rc.regions); wl_list_init(&rc.regions);
wl_list_init(&rc.window_switcher.osd.fields); wl_list_init(&rc.window_switcher.osd.fields);
wl_list_init(&rc.window_rules); wl_list_init(&rc.window_rules);
@ -1843,7 +1845,7 @@ post_processing(void)
assert(l && libinput_category_get_default() == l); assert(l && libinput_category_get_default() == l);
} }
if (mouse_scroll_factor >= 0) { if (mouse_scroll_factor >= 0) {
wlr_log(WLR_ERROR, "<mouse><scrollFactor> is deprecated" lab_wlr_log(WLR_ERROR, "<mouse><scrollFactor> is deprecated"
" and overwrites <libinput><scrollFactor>." " and overwrites <libinput><scrollFactor>."
" Use only <libinput><scrollFactor>."); " Use only <libinput><scrollFactor>.");
struct libinput_category *l; struct libinput_category *l;
@ -1903,7 +1905,7 @@ validate_actions(void)
if (!action_is_valid(action)) { if (!action_is_valid(action)) {
wl_list_remove(&action->link); wl_list_remove(&action->link);
action_free(action); action_free(action);
wlr_log(WLR_ERROR, "Removed invalid keybind action"); lab_wlr_log(WLR_ERROR, "Removed invalid keybind action");
} }
} }
} }
@ -1914,7 +1916,7 @@ validate_actions(void)
if (!action_is_valid(action)) { if (!action_is_valid(action)) {
wl_list_remove(&action->link); wl_list_remove(&action->link);
action_free(action); action_free(action);
wlr_log(WLR_ERROR, "Removed invalid mousebind action"); lab_wlr_log(WLR_ERROR, "Removed invalid mousebind action");
} }
} }
} }
@ -1925,7 +1927,7 @@ validate_actions(void)
if (!action_is_valid(action)) { if (!action_is_valid(action)) {
wl_list_remove(&action->link); wl_list_remove(&action->link);
action_free(action); action_free(action);
wlr_log(WLR_ERROR, "Removed invalid window rule action"); lab_wlr_log(WLR_ERROR, "Removed invalid window rule action");
} }
} }
} }
@ -1944,7 +1946,7 @@ validate(void)
|| box.width <= 0 || box.width > 100 || box.width <= 0 || box.width > 100
|| box.height <= 0 || box.height > 100; || box.height <= 0 || box.height > 100;
if (invalid) { if (invalid) {
wlr_log(WLR_ERROR, lab_wlr_log(WLR_ERROR,
"Removing invalid region '%s': %d%% x %d%% @ %d%%,%d%%", "Removing invalid region '%s': %d%% x %d%% @ %d%%,%d%%",
region->name, box.width, box.height, box.x, box.y); region->name, box.width, box.height, box.x, box.y);
wl_list_remove(&region->link); wl_list_remove(&region->link);
@ -1958,7 +1960,7 @@ validate(void)
wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) { wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) {
if (!rule->identifier && !rule->title && rule->window_type < 0 if (!rule->identifier && !rule->title && rule->window_type < 0
&& !rule->sandbox_engine && !rule->sandbox_app_id) { && !rule->sandbox_engine && !rule->sandbox_app_id) {
wlr_log(WLR_ERROR, "Deleting rule %p as it has no criteria", rule); lab_wlr_log(WLR_ERROR, "Deleting rule %p as it has no criteria", rule);
rule_destroy(rule); rule_destroy(rule);
} }
} }
@ -1971,7 +1973,7 @@ validate(void)
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) { wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) {
field_width_sum += field->width; field_width_sum += field->width;
if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) { if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) {
wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field); lab_wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field);
wl_list_remove(&field->link); wl_list_remove(&field->link);
cycle_osd_field_free(field); cycle_osd_field_free(field);
} }
@ -2017,7 +2019,7 @@ rcxml_read(const char *filename)
continue; continue;
} }
wlr_log(WLR_INFO, "read config file %s", path->string); lab_wlr_log(WLR_INFO, "read config file %s", path->string);
rcxml_parse_xml(&b); rcxml_parse_xml(&b);
buf_reset(&b); buf_reset(&b);
@ -2092,6 +2094,15 @@ rcxml_finish(void)
zfree(w); zfree(w);
} }
struct log_item *log_item, *log_tmp;
wl_list_for_each_safe(log_item, log_tmp, &rc.error_logs, link) {
wl_list_remove(&log_item->link);
zfree(log_item->text);
zfree(log_item);
}
rc.has_error = false;
dialog_destroy(NULL);
regions_destroy(NULL, &rc.regions); regions_destroy(NULL, &rc.regions);
clear_window_switcher_fields(); clear_window_switcher_fields();

View file

@ -426,6 +426,10 @@ get_cursor_context(void)
ret.type = desc->type; ret.type = desc->type;
} }
return ret;
case LAB_NODE_CONFIG_DIALOG:
ret.type = LAB_NODE_CONFIG_DIALOG;
ret.node = node;
return ret; return ret;
default: default:
/* Other node types are not attached a scene node */ /* Other node types are not attached a scene node */

View file

@ -17,6 +17,7 @@
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/region.h> #include <wlr/util/region.h>
#include "action.h" #include "action.h"
#include "config/dialog.h"
#include "common/macros.h" #include "common/macros.h"
#include "common/mem.h" #include "common/mem.h"
#include "config/mousebind.h" #include "config/mousebind.h"
@ -1236,7 +1237,10 @@ cursor_process_button_release(struct seat *seat, uint32_t button,
} }
return notify; return notify;
} }
if (ctx.type == LAB_NODE_CONFIG_DIALOG) {
dialog_destroy(ctx.node);
return notify;
}
if (server.input_mode != LAB_INPUT_STATE_PASSTHROUGH) { if (server.input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
return notify; return notify;
} }

View file

@ -7,6 +7,7 @@
#include "common/fd-util.h" #include "common/fd-util.h"
#include "common/font.h" #include "common/font.h"
#include "common/spawn.h" #include "common/spawn.h"
#include "config/dialog.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "config/session.h" #include "config/session.h"
#include "labwc.h" #include "labwc.h"
@ -263,6 +264,10 @@ main(int argc, char *argv[])
menu_init(); menu_init();
if (rc.has_error) {
wl_event_loop_add_idle(server.wl_event_loop, dialog_create_callback, NULL);
}
/* Delay startup of applications until the event loop is ready */ /* Delay startup of applications until the event loop is ready */
struct idle_ctx idle_ctx = { struct idle_ctx idle_ctx = {
.primary_client = primary_client, .primary_client = primary_client,

View file

@ -54,6 +54,7 @@
#include "common/macros.h" #include "common/macros.h"
#include "common/mem.h" #include "common/mem.h"
#include "common/scene-helpers.h" #include "common/scene-helpers.h"
#include "config/dialog.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "config/session.h" #include "config/session.h"
#include "decorations.h" #include "decorations.h"
@ -118,6 +119,9 @@ reload_config_and_theme(void)
resize_indicator_reconfigure(); resize_indicator_reconfigure();
kde_server_decoration_update_default(); kde_server_decoration_update_default();
workspaces_reconfigure(); workspaces_reconfigure();
if (rc.has_error) {
wl_event_loop_add_idle(server.wl_event_loop, dialog_create_callback, NULL);
}
} }
static int static int
@ -616,6 +620,7 @@ server_init(void)
server.wl_display, 1, server.renderer); server.wl_display, 1, server.renderer);
server.workspace_tree = lab_wlr_scene_tree_create(&server.scene->tree); server.workspace_tree = lab_wlr_scene_tree_create(&server.scene->tree);
server.dialog_tree = lab_wlr_scene_tree_create(&server.scene->tree);
server.xdg_popup_tree = lab_wlr_scene_tree_create(&server.scene->tree); server.xdg_popup_tree = lab_wlr_scene_tree_create(&server.scene->tree);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
// Creating/setting this is harmless when xwayland support is built-in // Creating/setting this is harmless when xwayland support is built-in