2021-09-24 21:45:48 +01:00
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2020-10-19 22:14:17 +01:00
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2021-02-17 20:38:16 +00:00
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
#include <libxml/parser.h>
|
|
|
|
|
|
#include <libxml/tree.h>
|
2023-10-31 21:36:45 +00:00
|
|
|
|
#include <signal.h>
|
2020-10-19 22:14:17 +01:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include <string.h>
|
2021-02-17 20:38:16 +00:00
|
|
|
|
#include <strings.h>
|
2022-06-12 21:22:01 +02:00
|
|
|
|
#include <wayland-server-core.h>
|
2024-11-26 06:42:08 +09:00
|
|
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
2021-07-22 21:30:17 +01:00
|
|
|
|
#include <wlr/util/log.h>
|
2022-10-05 08:43:56 +02:00
|
|
|
|
#include "action.h"
|
2021-02-17 20:38:16 +00:00
|
|
|
|
#include "common/buf.h"
|
config: support merging multiple config files
Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.
For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).
When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.
XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.
ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.
This change affects the following config/theme files:
- rc.xml
- menu.xml
- autostart
- environment
- themerc
- themerc-override
- Theme buttons, for example max.xbm
Instead of caching global config/theme directories, create lists of paths
(e.g. '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.
Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.
Avoid using stat() in most situations and just go straight to fopen().
Fixes #1406
2024-01-09 22:00:45 +00:00
|
|
|
|
#include "common/dir.h"
|
2020-10-21 20:32:08 +01:00
|
|
|
|
#include "common/font.h"
|
2022-10-05 08:43:56 +02:00
|
|
|
|
#include "common/list.h"
|
2024-11-10 18:54:01 +09:00
|
|
|
|
#include "common/macros.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
|
#include "common/mem.h"
|
2021-02-17 20:38:16 +00:00
|
|
|
|
#include "common/nodename.h"
|
2024-05-22 09:56:16 +09:00
|
|
|
|
#include "common/scaled-font-buffer.h"
|
2024-11-24 14:40:40 +09:00
|
|
|
|
#include "common/scaled-rect-buffer.h"
|
2022-12-05 14:38:16 +01:00
|
|
|
|
#include "common/scene-helpers.h"
|
2023-10-31 21:36:45 +00:00
|
|
|
|
#include "common/spawn.h"
|
2021-02-17 20:38:16 +00:00
|
|
|
|
#include "common/string-helpers.h"
|
2020-10-19 22:14:17 +01:00
|
|
|
|
#include "labwc.h"
|
|
|
|
|
|
#include "menu/menu.h"
|
2024-09-17 06:52:10 -05:00
|
|
|
|
#include "workspaces.h"
|
|
|
|
|
|
#include "view.h"
|
2022-03-03 04:33:33 +01:00
|
|
|
|
#include "node.h"
|
2022-10-05 08:43:56 +02:00
|
|
|
|
#include "theme.h"
|
2020-10-19 22:14:17 +01:00
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
#define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */
|
|
|
|
|
|
#define PIPEMENU_TIMEOUT_IN_MS 4000 /* 4 seconds */
|
|
|
|
|
|
|
2021-02-17 20:38:16 +00:00
|
|
|
|
/* state-machine variables for processing <item></item> */
|
2021-09-24 22:14:04 +01:00
|
|
|
|
static bool in_item;
|
2021-02-17 20:38:16 +00:00
|
|
|
|
static struct menuitem *current_item;
|
2022-01-05 09:11:24 +01:00
|
|
|
|
static struct action *current_item_action;
|
2021-02-17 20:38:16 +00:00
|
|
|
|
|
2021-11-08 17:20:37 +00:00
|
|
|
|
static int menu_level;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
static struct menu *current_menu;
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
static bool waiting_for_pipe_menu;
|
|
|
|
|
|
static struct menuitem *selected_item;
|
|
|
|
|
|
|
2024-08-20 18:09:17 +02:00
|
|
|
|
struct menu_pipe_context {
|
|
|
|
|
|
struct server *server;
|
|
|
|
|
|
struct menuitem *item;
|
|
|
|
|
|
struct buf buf;
|
|
|
|
|
|
struct wl_event_source *event_read;
|
|
|
|
|
|
struct wl_event_source *event_timeout;
|
|
|
|
|
|
pid_t pid;
|
|
|
|
|
|
int pipe_fd;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-08-30 11:20:46 +02:00
|
|
|
|
/* TODO: split this whole file into parser.c and actions.c*/
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
static bool
|
|
|
|
|
|
is_unique_id(struct server *server, const char *id)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu;
|
|
|
|
|
|
wl_list_for_each(menu, &server->menus, link) {
|
|
|
|
|
|
if (!strcmp(menu->id, id)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
static struct menu *
|
|
|
|
|
|
menu_create(struct server *server, const char *id, const char *label)
|
|
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (!is_unique_id(server, id)) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "menu id %s already exists", id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu = znew(*menu);
|
|
|
|
|
|
wl_list_append(&server->menus, &menu->link);
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
wl_list_init(&menu->menuitems);
|
2022-09-16 18:41:02 -04:00
|
|
|
|
menu->id = xstrdup(id);
|
|
|
|
|
|
menu->label = xstrdup(label ? label : id);
|
2021-11-08 17:20:37 +00:00
|
|
|
|
menu->parent = current_menu;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
menu->server = server;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
menu->is_pipemenu = waiting_for_pipe_menu;
|
2022-12-05 14:38:16 +01:00
|
|
|
|
menu->size.width = server->theme->menu_min_width;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-19 02:05:38 +01:00
|
|
|
|
struct menu *
|
2023-10-09 20:59:04 +01:00
|
|
|
|
menu_get_by_id(struct server *server, const char *id)
|
2021-11-02 18:31:19 +00:00
|
|
|
|
{
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (!id) {
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
struct menu *menu;
|
2023-10-09 20:59:04 +01:00
|
|
|
|
wl_list_for_each(menu, &server->menus, link) {
|
2021-11-02 18:31:19 +00:00
|
|
|
|
if (!strcmp(menu->id, id)) {
|
|
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
2020-10-19 22:14:17 +01:00
|
|
|
|
|
2023-05-13 16:27:46 +02:00
|
|
|
|
static void
|
|
|
|
|
|
validate_menu(struct menu *menu)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menuitem *item;
|
|
|
|
|
|
struct action *action, *action_tmp;
|
|
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
|
|
|
|
|
wl_list_for_each_safe(action, action_tmp, &item->actions, link) {
|
|
|
|
|
|
if (!action_is_valid(action)) {
|
|
|
|
|
|
wl_list_remove(&action->link);
|
|
|
|
|
|
action_free(action);
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Removed invalid menu action");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
validate(struct server *server)
|
|
|
|
|
|
{
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu;
|
|
|
|
|
|
wl_list_for_each(menu, &server->menus, link) {
|
|
|
|
|
|
validate_menu(menu);
|
2023-05-13 16:27:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-01 19:21:09 +01:00
|
|
|
|
static struct menuitem *
|
2022-08-02 22:00:24 +01:00
|
|
|
|
item_create(struct menu *menu, const char *text, bool show_arrow)
|
2020-10-19 22:14:17 +01:00
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
assert(menu);
|
|
|
|
|
|
assert(text);
|
|
|
|
|
|
|
2022-09-18 15:22:26 -04:00
|
|
|
|
struct menuitem *menuitem = znew(*menuitem);
|
2022-03-03 04:33:33 +01:00
|
|
|
|
menuitem->parent = menu;
|
2022-06-22 21:07:25 +01:00
|
|
|
|
menuitem->selectable = true;
|
2024-05-09 21:20:15 +01:00
|
|
|
|
menuitem->type = LAB_MENU_ITEM;
|
2024-11-10 18:54:01 +09:00
|
|
|
|
menuitem->text = xstrdup(text);
|
|
|
|
|
|
menuitem->arrow = show_arrow ? "›" : NULL;
|
2021-08-07 08:35:46 +01:00
|
|
|
|
|
2022-12-05 14:38:16 +01:00
|
|
|
|
menuitem->native_width = font_width(&rc.font_menuitem, text);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
if (menuitem->arrow) {
|
|
|
|
|
|
menuitem->native_width += font_width(&rc.font_menuitem, menuitem->arrow);
|
2022-12-05 14:38:16 +01:00
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
wl_list_append(&menu->menuitems, &menuitem->link);
|
|
|
|
|
|
wl_list_init(&menuitem->actions);
|
|
|
|
|
|
return menuitem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-03 15:58:07 +09:00
|
|
|
|
static struct wlr_scene_tree *
|
|
|
|
|
|
item_create_scene_for_state(struct menuitem *item, float *text_color,
|
|
|
|
|
|
float *bg_color)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = item->parent;
|
|
|
|
|
|
struct theme *theme = menu->server->theme;
|
|
|
|
|
|
|
|
|
|
|
|
/* Tree to hold background and label buffers */
|
|
|
|
|
|
struct wlr_scene_tree *tree = wlr_scene_tree_create(item->tree);
|
|
|
|
|
|
|
|
|
|
|
|
/* Create background */
|
|
|
|
|
|
int bg_width = menu->size.width
|
|
|
|
|
|
- 2 * theme->menu_border_width;
|
|
|
|
|
|
wlr_scene_rect_create(tree, bg_width, theme->menu_item_height, bg_color);
|
|
|
|
|
|
|
|
|
|
|
|
int arrow_width = item->arrow ?
|
|
|
|
|
|
font_width(&rc.font_menuitem, item->arrow) : 0;
|
2024-12-03 16:09:40 +09:00
|
|
|
|
int label_max_width = bg_width - 2 * theme->menu_items_padding_x - arrow_width;
|
2024-12-03 15:58:07 +09:00
|
|
|
|
|
|
|
|
|
|
/* Create label */
|
|
|
|
|
|
struct scaled_font_buffer *label_buffer = scaled_font_buffer_create(tree);
|
|
|
|
|
|
assert(label_buffer);
|
2024-12-03 16:09:40 +09:00
|
|
|
|
scaled_font_buffer_update(label_buffer, item->text, label_max_width,
|
|
|
|
|
|
&rc.font_menuitem, text_color, bg_color);
|
2024-12-03 15:58:07 +09:00
|
|
|
|
/* Vertically center and left-align label */
|
|
|
|
|
|
int x = theme->menu_items_padding_x;
|
|
|
|
|
|
int y = (theme->menu_item_height - label_buffer->height) / 2;
|
|
|
|
|
|
wlr_scene_node_set_position(&label_buffer->scene_buffer->node, x, y);
|
|
|
|
|
|
|
2024-12-03 16:09:40 +09:00
|
|
|
|
if (!item->arrow) {
|
|
|
|
|
|
return tree;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Create arrow for submenu items */
|
|
|
|
|
|
struct scaled_font_buffer *arrow_buffer = scaled_font_buffer_create(tree);
|
|
|
|
|
|
assert(arrow_buffer);
|
|
|
|
|
|
scaled_font_buffer_update(arrow_buffer, item->arrow, -1,
|
|
|
|
|
|
&rc.font_menuitem, text_color, bg_color);
|
|
|
|
|
|
/* Vertically center and right-align arrow */
|
|
|
|
|
|
x += label_max_width;
|
|
|
|
|
|
y = (theme->menu_item_height - label_buffer->height) / 2;
|
|
|
|
|
|
wlr_scene_node_set_position(&arrow_buffer->scene_buffer->node, x, y);
|
|
|
|
|
|
|
2024-12-03 15:58:07 +09:00
|
|
|
|
return tree;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
static void
|
|
|
|
|
|
item_create_scene(struct menuitem *menuitem, int *item_y)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(menuitem);
|
|
|
|
|
|
assert(menuitem->type == LAB_MENU_ITEM);
|
|
|
|
|
|
struct menu *menu = menuitem->parent;
|
|
|
|
|
|
struct theme *theme = menu->server->theme;
|
|
|
|
|
|
|
2022-06-05 14:42:06 +02:00
|
|
|
|
/* Menu item root node */
|
2022-06-05 15:17:35 +02:00
|
|
|
|
menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
|
2022-06-05 14:42:06 +02:00
|
|
|
|
node_descriptor_create(&menuitem->tree->node,
|
|
|
|
|
|
LAB_NODE_DESC_MENUITEM, menuitem);
|
|
|
|
|
|
|
2024-12-03 15:58:07 +09:00
|
|
|
|
/* Create scenes for unselected/selected states */
|
|
|
|
|
|
menuitem->normal_tree = item_create_scene_for_state(menuitem,
|
2024-11-10 18:54:01 +09:00
|
|
|
|
theme->menu_items_text_color,
|
2024-12-03 15:58:07 +09:00
|
|
|
|
theme->menu_items_bg_color);
|
|
|
|
|
|
menuitem->selected_tree = item_create_scene_for_state(menuitem,
|
2024-11-10 18:54:01 +09:00
|
|
|
|
theme->menu_items_active_text_color,
|
2024-12-03 15:58:07 +09:00
|
|
|
|
theme->menu_items_active_bg_color);
|
|
|
|
|
|
/* Hide selected state */
|
|
|
|
|
|
wlr_scene_node_set_enabled(&menuitem->selected_tree->node, false);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
|
|
|
|
|
|
/* Position the item in relation to its menu */
|
2024-11-24 14:40:40 +09:00
|
|
|
|
wlr_scene_node_set_position(&menuitem->tree->node,
|
|
|
|
|
|
theme->menu_border_width, *item_y);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
*item_y += theme->menu_item_height;
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-22 21:07:25 +01:00
|
|
|
|
static struct menuitem *
|
|
|
|
|
|
separator_create(struct menu *menu, const char *label)
|
|
|
|
|
|
{
|
2022-09-18 15:22:26 -04:00
|
|
|
|
struct menuitem *menuitem = znew(*menuitem);
|
2022-06-22 21:07:25 +01:00
|
|
|
|
menuitem->parent = menu;
|
|
|
|
|
|
menuitem->selectable = false;
|
2024-05-09 21:20:15 +01:00
|
|
|
|
menuitem->type = string_null_or_empty(label) ? LAB_MENU_SEPARATOR_LINE
|
|
|
|
|
|
: LAB_MENU_TITLE;
|
|
|
|
|
|
if (menuitem->type == LAB_MENU_TITLE) {
|
2024-11-10 18:54:01 +09:00
|
|
|
|
menuitem->text = xstrdup(label);
|
2024-08-21 12:27:07 -05:00
|
|
|
|
menuitem->native_width = font_width(&rc.font_menuheader, label);
|
2024-05-09 21:20:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
wl_list_append(&menu->menuitems, &menuitem->link);
|
|
|
|
|
|
wl_list_init(&menuitem->actions);
|
|
|
|
|
|
return menuitem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
separator_create_scene(struct menuitem *menuitem, int *item_y)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(menuitem);
|
|
|
|
|
|
assert(menuitem->type == LAB_MENU_SEPARATOR_LINE);
|
|
|
|
|
|
struct menu *menu = menuitem->parent;
|
|
|
|
|
|
struct theme *theme = menu->server->theme;
|
2024-11-24 14:40:40 +09:00
|
|
|
|
int bg_width = menu->size.width - 2 * theme->menu_border_width;
|
|
|
|
|
|
int bg_height = theme->menu_separator_line_thickness
|
2024-11-10 18:54:01 +09:00
|
|
|
|
+ 2 * theme->menu_separator_padding_height;
|
|
|
|
|
|
|
2024-05-09 21:20:15 +01:00
|
|
|
|
/* Menu item root node */
|
2022-06-22 21:07:25 +01:00
|
|
|
|
menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
|
|
|
|
|
|
node_descriptor_create(&menuitem->tree->node,
|
|
|
|
|
|
LAB_NODE_DESC_MENUITEM, menuitem);
|
2024-05-09 21:20:15 +01:00
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
/* Tree to hold background and line buffer */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
menuitem->normal_tree = wlr_scene_tree_create(menuitem->tree);
|
2022-06-22 21:07:25 +01:00
|
|
|
|
|
2024-05-09 21:20:15 +01:00
|
|
|
|
/* Item background nodes */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
wlr_scene_rect_create(menuitem->normal_tree, bg_width, bg_height,
|
|
|
|
|
|
theme->menu_items_bg_color);
|
2024-05-09 21:20:15 +01:00
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
/* Draw separator line */
|
2024-11-24 14:40:40 +09:00
|
|
|
|
int line_width = bg_width - 2 * theme->menu_separator_padding_width;
|
2024-12-03 15:58:07 +09:00
|
|
|
|
struct wlr_scene_rect *line_rect = wlr_scene_rect_create(
|
|
|
|
|
|
menuitem->normal_tree, line_width,
|
2024-11-10 18:54:01 +09:00
|
|
|
|
theme->menu_separator_line_thickness,
|
2024-12-03 15:58:07 +09:00
|
|
|
|
theme->menu_separator_color);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
/* Vertically center-align separator line */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
wlr_scene_node_set_position(&line_rect->node,
|
2024-11-10 18:54:01 +09:00
|
|
|
|
theme->menu_separator_padding_width,
|
|
|
|
|
|
theme->menu_separator_padding_height);
|
2024-11-24 14:40:40 +09:00
|
|
|
|
wlr_scene_node_set_position(&menuitem->tree->node,
|
|
|
|
|
|
theme->menu_border_width, *item_y);
|
|
|
|
|
|
*item_y += bg_height;
|
2024-11-10 18:54:01 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
title_create_scene(struct menuitem *menuitem, int *item_y)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(menuitem);
|
|
|
|
|
|
assert(menuitem->type == LAB_MENU_TITLE);
|
|
|
|
|
|
struct menu *menu = menuitem->parent;
|
|
|
|
|
|
struct theme *theme = menu->server->theme;
|
|
|
|
|
|
float *bg_color = theme->menu_title_bg_color;
|
|
|
|
|
|
float *text_color = theme->menu_title_text_color;
|
2024-11-24 14:40:40 +09:00
|
|
|
|
int bg_width = menu->size.width - 2 * theme->menu_border_width;
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
/* Menu item root node */
|
|
|
|
|
|
menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
|
|
|
|
|
|
node_descriptor_create(&menuitem->tree->node,
|
|
|
|
|
|
LAB_NODE_DESC_MENUITEM, menuitem);
|
|
|
|
|
|
|
|
|
|
|
|
/* Tree to hold background and text buffer */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
menuitem->normal_tree = wlr_scene_tree_create(menuitem->tree);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
/* Background */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
wlr_scene_rect_create(menuitem->normal_tree,
|
|
|
|
|
|
bg_width, theme->menu_header_height, bg_color);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
/* Draw separator title */
|
2024-12-03 15:58:07 +09:00
|
|
|
|
struct scaled_font_buffer *title_font_buffer =
|
|
|
|
|
|
scaled_font_buffer_create(menuitem->normal_tree);
|
|
|
|
|
|
assert(title_font_buffer);
|
|
|
|
|
|
scaled_font_buffer_update(title_font_buffer, menuitem->text,
|
2024-11-24 14:40:40 +09:00
|
|
|
|
bg_width - 2 * theme->menu_items_padding_x,
|
2024-12-03 16:09:40 +09:00
|
|
|
|
&rc.font_menuheader, text_color, bg_color);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
int title_x = 0;
|
2024-11-13 05:14:53 +09:00
|
|
|
|
switch (theme->menu_title_text_justify) {
|
|
|
|
|
|
case LAB_JUSTIFY_CENTER:
|
2024-11-24 14:40:40 +09:00
|
|
|
|
title_x = (bg_width - menuitem->native_width) / 2;
|
2024-11-10 18:54:01 +09:00
|
|
|
|
title_x = MAX(title_x, 0);
|
2024-11-13 05:14:53 +09:00
|
|
|
|
break;
|
|
|
|
|
|
case LAB_JUSTIFY_LEFT:
|
2024-11-10 18:54:01 +09:00
|
|
|
|
title_x = theme->menu_items_padding_x;
|
2024-11-13 05:14:53 +09:00
|
|
|
|
break;
|
|
|
|
|
|
case LAB_JUSTIFY_RIGHT:
|
2024-11-24 14:40:40 +09:00
|
|
|
|
title_x = bg_width - menuitem->native_width
|
2024-11-13 05:14:53 +09:00
|
|
|
|
- theme->menu_items_padding_x;
|
|
|
|
|
|
break;
|
2024-05-09 21:20:15 +01:00
|
|
|
|
}
|
2024-12-03 15:58:07 +09:00
|
|
|
|
int title_y = (theme->menu_header_height - title_font_buffer->height) / 2;
|
|
|
|
|
|
wlr_scene_node_set_position(&title_font_buffer->scene_buffer->node,
|
|
|
|
|
|
title_x, title_y);
|
2022-06-22 21:07:25 +01:00
|
|
|
|
|
2024-11-24 14:40:40 +09:00
|
|
|
|
wlr_scene_node_set_position(&menuitem->tree->node,
|
|
|
|
|
|
theme->menu_border_width, *item_y);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
*item_y += theme->menu_header_height;
|
2022-06-22 21:07:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-14 08:14:11 +09:00
|
|
|
|
/* (Re)creates the scene of the menu */
|
2024-11-10 18:51:48 +09:00
|
|
|
|
static void
|
2024-11-10 18:54:01 +09:00
|
|
|
|
menu_update_scene(struct menu *menu)
|
2024-11-10 18:51:48 +09:00
|
|
|
|
{
|
|
|
|
|
|
struct menuitem *item;
|
|
|
|
|
|
struct theme *theme = menu->server->theme;
|
2024-11-10 18:54:01 +09:00
|
|
|
|
|
|
|
|
|
|
if (menu->scene_tree) {
|
|
|
|
|
|
wlr_scene_node_destroy(&menu->scene_tree->node);
|
|
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
|
|
|
|
|
item->tree = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
menu->scene_tree = wlr_scene_tree_create(menu->server->menu_tree);
|
|
|
|
|
|
wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
|
2024-11-10 18:51:48 +09:00
|
|
|
|
|
2024-11-13 05:34:44 +09:00
|
|
|
|
/* Menu width is the maximum item width, capped by menu.width.{min,max} */
|
|
|
|
|
|
menu->size.width = 0;
|
2024-11-10 18:51:48 +09:00
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
2024-11-24 14:40:40 +09:00
|
|
|
|
int width = item->native_width
|
|
|
|
|
|
+ 2 * theme->menu_items_padding_x
|
|
|
|
|
|
+ 2 * theme->menu_border_width;
|
|
|
|
|
|
menu->size.width = MAX(menu->size.width, width);
|
2024-11-10 18:51:48 +09:00
|
|
|
|
}
|
2024-11-13 05:34:44 +09:00
|
|
|
|
menu->size.width = MAX(menu->size.width, theme->menu_min_width);
|
|
|
|
|
|
menu->size.width = MIN(menu->size.width, theme->menu_max_width);
|
2024-11-10 18:51:48 +09:00
|
|
|
|
|
|
|
|
|
|
/* Update all items for the new size */
|
2024-11-24 14:40:40 +09:00
|
|
|
|
int item_y = theme->menu_border_width;
|
2024-11-10 18:51:48 +09:00
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
2024-11-10 18:54:01 +09:00
|
|
|
|
assert(!item->tree);
|
|
|
|
|
|
switch (item->type) {
|
|
|
|
|
|
case LAB_MENU_ITEM:
|
|
|
|
|
|
item_create_scene(item, &item_y);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LAB_MENU_SEPARATOR_LINE:
|
|
|
|
|
|
separator_create_scene(item, &item_y);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LAB_MENU_TITLE:
|
|
|
|
|
|
title_create_scene(item, &item_y);
|
|
|
|
|
|
break;
|
2024-11-10 18:51:48 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-24 14:40:40 +09:00
|
|
|
|
menu->size.height = item_y + theme->menu_border_width;
|
|
|
|
|
|
|
|
|
|
|
|
float transparent[4] = {0};
|
|
|
|
|
|
struct scaled_rect_buffer *bg_buffer = scaled_rect_buffer_create(
|
|
|
|
|
|
menu->scene_tree, menu->size.width, menu->size.height,
|
|
|
|
|
|
theme->menu_border_width, transparent,
|
|
|
|
|
|
theme->menu_border_color);
|
|
|
|
|
|
assert(bg_buffer);
|
|
|
|
|
|
wlr_scene_node_lower_to_bottom(&bg_buffer->scene_buffer->node);
|
2024-11-10 18:51:48 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
post_processing(struct server *server)
|
|
|
|
|
|
{
|
2024-11-14 08:14:11 +09:00
|
|
|
|
/*
|
|
|
|
|
|
* Create menu scene after all of its contents is determined
|
|
|
|
|
|
* (e.g. when finished reading menu.xml or received output from
|
|
|
|
|
|
* pipemenu program).
|
|
|
|
|
|
*/
|
2024-11-10 18:51:48 +09:00
|
|
|
|
struct menu *menu;
|
|
|
|
|
|
wl_list_for_each(menu, &server->menus, link) {
|
2024-11-14 08:14:11 +09:00
|
|
|
|
if (!menu->scene_tree) {
|
|
|
|
|
|
menu_update_scene(menu);
|
|
|
|
|
|
}
|
2024-11-10 18:51:48 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* Handle the following:
|
|
|
|
|
|
* <item label="">
|
|
|
|
|
|
* <action name="">
|
|
|
|
|
|
* <command></command>
|
|
|
|
|
|
* </action>
|
|
|
|
|
|
* </item>
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void
|
|
|
|
|
|
fill_item(char *nodename, char *content)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* Nodenames for most menu-items end with '.item.menu' but top-level
|
|
|
|
|
|
* pipemenu items do not have the associated <menu> element so merely
|
|
|
|
|
|
* end with a '.item'
|
|
|
|
|
|
*/
|
2021-02-17 20:38:16 +00:00
|
|
|
|
string_truncate_at_pattern(nodename, ".item.menu");
|
2023-10-31 21:36:45 +00:00
|
|
|
|
string_truncate_at_pattern(nodename, ".item");
|
2021-02-17 20:38:16 +00:00
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
/* <item label=""> defines the start of a new item */
|
2021-02-17 20:38:16 +00:00
|
|
|
|
if (!strcmp(nodename, "label")) {
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(current_menu, content, false);
|
2022-01-06 03:24:32 +01:00
|
|
|
|
current_item_action = NULL;
|
2022-01-05 09:11:24 +01:00
|
|
|
|
} else if (!current_item) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "expect <item label=\"\"> element first. "
|
|
|
|
|
|
"nodename: '%s' content: '%s'", nodename, content);
|
2022-05-06 21:52:46 +01:00
|
|
|
|
} else if (!strcmp(nodename, "icon")) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Do nothing as we don't support menu icons - just avoid
|
|
|
|
|
|
* logging errors if a menu.xml file contains icon="" entries.
|
|
|
|
|
|
*/
|
2022-01-05 09:11:24 +01:00
|
|
|
|
} else if (!strcmp(nodename, "name.action")) {
|
|
|
|
|
|
current_item_action = action_create(content);
|
2023-01-29 04:06:46 +01:00
|
|
|
|
if (current_item_action) {
|
|
|
|
|
|
wl_list_append(¤t_item->actions,
|
|
|
|
|
|
¤t_item_action->link);
|
|
|
|
|
|
}
|
2022-01-05 09:11:24 +01:00
|
|
|
|
} else if (!current_item_action) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. "
|
|
|
|
|
|
"nodename: '%s' content: '%s'", nodename, content);
|
2022-12-10 15:28:25 +01:00
|
|
|
|
} else {
|
|
|
|
|
|
action_arg_from_xml_node(current_item_action, nodename, content);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:49:55 +02:00
|
|
|
|
static void
|
|
|
|
|
|
item_destroy(struct menuitem *item)
|
|
|
|
|
|
{
|
2024-08-20 18:09:17 +02:00
|
|
|
|
if (item->pipe_ctx) {
|
|
|
|
|
|
item->pipe_ctx->item = NULL;
|
|
|
|
|
|
}
|
2022-06-15 01:49:55 +02:00
|
|
|
|
wl_list_remove(&item->link);
|
|
|
|
|
|
action_list_free(&item->actions);
|
2024-11-14 18:16:43 +09:00
|
|
|
|
if (item->tree) {
|
|
|
|
|
|
wlr_scene_node_destroy(&item->tree->node);
|
|
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
free(item->execute);
|
|
|
|
|
|
free(item->id);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
free(item->text);
|
2022-06-15 01:49:55 +02:00
|
|
|
|
free(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-02 21:17:26 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* We support XML CDATA for <command> in menu.xml in order to provide backward
|
|
|
|
|
|
* compatibility with obmenu-generator. For example:
|
|
|
|
|
|
*
|
|
|
|
|
|
* <menu id="" label="">
|
|
|
|
|
|
* <item label="">
|
|
|
|
|
|
* <action name="Execute">
|
|
|
|
|
|
* <command><![CDATA[xdg-open .]]></command>
|
|
|
|
|
|
* </action>
|
|
|
|
|
|
* </item>
|
|
|
|
|
|
* </menu>
|
|
|
|
|
|
*
|
|
|
|
|
|
* <execute> is an old, deprecated openbox variety of <command>. We support it
|
|
|
|
|
|
* for backward compatibility with old openbox-menu generators. It has the same
|
|
|
|
|
|
* function and <command>
|
|
|
|
|
|
*
|
2023-10-31 21:36:45 +00:00
|
|
|
|
* The following nodenames support CDATA.
|
|
|
|
|
|
* - command.action.item.*menu.openbox_menu
|
|
|
|
|
|
* - execute.action.item.*menu.openbox_menu
|
|
|
|
|
|
* - command.action.item.openbox_pipe_menu
|
|
|
|
|
|
* - execute.action.item.openbox_pipe_menu
|
|
|
|
|
|
* - command.action.item.*menu.openbox_pipe_menu
|
|
|
|
|
|
* - execute.action.item.*menu.openbox_pipe_menu
|
|
|
|
|
|
*
|
|
|
|
|
|
* The *menu allows nested menus with nodenames such as ...menu.menu... or
|
|
|
|
|
|
* ...menu.menu.menu... and so on. We could use match_glob() for all of the
|
|
|
|
|
|
* above but it seems simpler to just check the first three fields.
|
2023-07-02 21:17:26 +01:00
|
|
|
|
*/
|
|
|
|
|
|
static bool
|
|
|
|
|
|
nodename_supports_cdata(char *nodename)
|
|
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
return !strncmp("command.action.", nodename, 15)
|
|
|
|
|
|
|| !strncmp("execute.action.", nodename, 15);
|
2023-07-02 21:17:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-17 20:38:16 +00:00
|
|
|
|
static void
|
2021-11-02 18:31:19 +00:00
|
|
|
|
entry(xmlNode *node, char *nodename, char *content)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
2023-07-02 21:17:26 +01:00
|
|
|
|
if (!nodename) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
xmlChar *cdata = NULL;
|
|
|
|
|
|
if (!content && nodename_supports_cdata(nodename)) {
|
|
|
|
|
|
cdata = xmlNodeGetContent(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!content && !cdata) {
|
2021-02-17 20:38:16 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
string_truncate_at_pattern(nodename, ".openbox_menu");
|
2023-10-31 21:36:45 +00:00
|
|
|
|
string_truncate_at_pattern(nodename, ".openbox_pipe_menu");
|
2023-07-02 13:55:23 +01:00
|
|
|
|
if (getenv("LABWC_DEBUG_MENU_NODENAMES")) {
|
2023-07-02 21:17:26 +01:00
|
|
|
|
printf("%s: %s\n", nodename, content ? content : (char *)cdata);
|
2023-07-02 13:55:23 +01:00
|
|
|
|
}
|
2021-02-17 20:38:16 +00:00
|
|
|
|
if (in_item) {
|
2023-07-02 21:17:26 +01:00
|
|
|
|
fill_item(nodename, content ? content : (char *)cdata);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
2023-07-02 21:17:26 +01:00
|
|
|
|
xmlFree(cdata);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-11-02 18:31:19 +00:00
|
|
|
|
process_node(xmlNode *node)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
static char buffer[256];
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
char *content = (char *)node->content;
|
2021-02-17 20:38:16 +00:00
|
|
|
|
if (xmlIsBlankNode(node)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
char *name = nodename(node, buffer, sizeof(buffer));
|
|
|
|
|
|
entry(node, name, content);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
static void xml_tree_walk(xmlNode *node, struct server *server);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
|
|
|
|
|
|
static void
|
2021-11-02 18:31:19 +00:00
|
|
|
|
traverse(xmlNode *n, struct server *server)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
2021-09-24 22:14:04 +01:00
|
|
|
|
xmlAttr *attr;
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
process_node(n);
|
2021-09-24 22:14:04 +01:00
|
|
|
|
for (attr = n->properties; attr; attr = attr->next) {
|
2021-11-02 18:31:19 +00:00
|
|
|
|
xml_tree_walk(attr->children, server);
|
|
|
|
|
|
}
|
|
|
|
|
|
xml_tree_walk(n->children, server);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-11 18:43:51 +01:00
|
|
|
|
static int
|
|
|
|
|
|
nr_parents(xmlNode *n)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(n);
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
for (xmlNode *node = n->parent; node && i < INT_MAX; ++i) {
|
|
|
|
|
|
node = node->parent;
|
|
|
|
|
|
}
|
|
|
|
|
|
return i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* Return true for the highest level static menu definitions in the format
|
|
|
|
|
|
* below. We use the fact that the id-attribute has two nodal parents (<menu>
|
|
|
|
|
|
* and <openbox_menu>) as the test here.
|
|
|
|
|
|
*
|
|
|
|
|
|
* <openbox_menu>
|
|
|
|
|
|
* <menu id="">
|
|
|
|
|
|
* ...
|
|
|
|
|
|
* </menu>
|
|
|
|
|
|
* </openbox_menu>
|
|
|
|
|
|
*
|
|
|
|
|
|
* Return false for any other <menu id=""> element which could be either:
|
|
|
|
|
|
*
|
|
|
|
|
|
* (a) one found in a pipemenu; or
|
|
|
|
|
|
* (b) one that links to a submenu as follows (but is a child to another
|
|
|
|
|
|
* <menu> element.
|
|
|
|
|
|
*
|
|
|
|
|
|
* <menu id="root-menu">
|
|
|
|
|
|
* <!-- this links to a sub menu -->
|
|
|
|
|
|
* <menu id="submenu-defined-elsewhere"/>
|
|
|
|
|
|
* </menu>
|
|
|
|
|
|
*/
|
|
|
|
|
|
static bool
|
|
|
|
|
|
is_toplevel_static_menu_definition(xmlNode *n, char *id)
|
|
|
|
|
|
{
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Catch <menu id=""> elements in pipemenus
|
|
|
|
|
|
*
|
|
|
|
|
|
* For pipemenus we cannot just rely on nr_parents() because they have
|
|
|
|
|
|
* their own hierarchy, so we just use the fact that a pipemenu cannot
|
|
|
|
|
|
* be the root-menu.
|
|
|
|
|
|
*/
|
|
|
|
|
|
if (menu_level) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return id && nr_parents(n) == 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* <menu> elements have three different roles:
|
|
|
|
|
|
* * Definition of (sub)menu - has ID, LABEL and CONTENT
|
2023-10-31 21:36:45 +00:00
|
|
|
|
* * Menuitem of pipemenu type - has ID, LABEL and EXECUTE
|
2021-11-02 18:31:19 +00:00
|
|
|
|
* * Menuitem of submenu type - has ID only
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void
|
|
|
|
|
|
handle_menu_element(xmlNode *n, struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
|
|
|
|
|
|
char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute");
|
|
|
|
|
|
char *id = (char *)xmlGetProp(n, (const xmlChar *)"id");
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (execute && label && id) {
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
|
2024-07-15 21:47:10 +01:00
|
|
|
|
if (!current_menu) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* We currently do not support pipemenus without a
|
|
|
|
|
|
* parent <item> such as the one the example below:
|
|
|
|
|
|
*
|
|
|
|
|
|
* <?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
|
* <openbox_menu>
|
|
|
|
|
|
* <menu id="root-menu" label="foo" execute="bar"/>
|
|
|
|
|
|
* </openbox_menu>
|
|
|
|
|
|
*
|
|
|
|
|
|
* TODO: Consider supporting this
|
|
|
|
|
|
*/
|
|
|
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
|
"pipemenu '%s:%s:%s' has no parent <menu>",
|
|
|
|
|
|
id, label, execute);
|
|
|
|
|
|
goto error;
|
|
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
current_item = item_create(current_menu, label, /* arrow */ true);
|
|
|
|
|
|
current_item_action = NULL;
|
|
|
|
|
|
current_item->execute = xstrdup(execute);
|
|
|
|
|
|
current_item->id = xstrdup(id);
|
|
|
|
|
|
} else if ((label && id) || is_toplevel_static_menu_definition(n, id)) {
|
2022-08-11 18:43:51 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* (label && id) refers to <menu id="" label=""> which is an
|
|
|
|
|
|
* inline menu definition.
|
|
|
|
|
|
*
|
2023-10-31 21:36:45 +00:00
|
|
|
|
* is_toplevel_static_menu_definition() catches:
|
|
|
|
|
|
* <openbox_menu>
|
|
|
|
|
|
* <menu id=""></menu>
|
|
|
|
|
|
* </openbox_menu>
|
2022-08-11 18:43:51 +01:00
|
|
|
|
*
|
|
|
|
|
|
* which is the highest level a menu can be defined at.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Openbox spec requires a label="" defined here, but it is
|
|
|
|
|
|
* actually pointless so we handle it with or without the label
|
2024-03-08 21:59:20 +09:00
|
|
|
|
* attribute to make it easier for users to define "root-menu"
|
2022-08-11 18:43:51 +01:00
|
|
|
|
* and "client-menu".
|
|
|
|
|
|
*/
|
2021-11-08 17:20:37 +00:00
|
|
|
|
struct menu **submenu = NULL;
|
|
|
|
|
|
if (menu_level > 0) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* In a nested (inline) menu definition we need to
|
|
|
|
|
|
* create an item pointing to the new submenu
|
|
|
|
|
|
*/
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(current_menu, label, true);
|
2022-06-12 21:14:48 +02:00
|
|
|
|
if (current_item) {
|
|
|
|
|
|
submenu = ¤t_item->submenu;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
submenu = NULL;
|
|
|
|
|
|
}
|
2021-11-08 17:20:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
++menu_level;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
current_menu = menu_create(server, id, label);
|
2021-11-08 17:20:37 +00:00
|
|
|
|
if (submenu) {
|
|
|
|
|
|
*submenu = current_menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
traverse(n, server);
|
|
|
|
|
|
current_menu = current_menu->parent;
|
|
|
|
|
|
--menu_level;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
} else if (id) {
|
2022-08-11 18:43:51 +01:00
|
|
|
|
/*
|
2023-10-31 21:36:45 +00:00
|
|
|
|
* <menu id=""> (when inside another <menu> element) creates an
|
|
|
|
|
|
* entry which points to a menu defined elsewhere.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is only supported in static menus. Pipemenus need to use
|
|
|
|
|
|
* nested (inline) menu definitions, otherwise we could have a
|
|
|
|
|
|
* pipemenu opening the "root-menu" or similar.
|
2022-08-11 18:43:51 +01:00
|
|
|
|
*/
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
|
|
|
|
|
if (current_menu && current_menu->is_pipemenu) {
|
|
|
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
|
"cannot link to static menu from pipemenu");
|
|
|
|
|
|
goto error;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu = menu_get_by_id(server, id);
|
2024-11-14 00:25:49 +09:00
|
|
|
|
|
|
|
|
|
|
struct menu *iter = current_menu;
|
|
|
|
|
|
while (iter) {
|
|
|
|
|
|
if (iter == menu) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "menus with the same id '%s' "
|
|
|
|
|
|
"cannot be nested", id);
|
|
|
|
|
|
goto error;
|
|
|
|
|
|
}
|
|
|
|
|
|
iter = iter->parent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
if (menu) {
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(current_menu, menu->label, true);
|
2022-06-12 21:14:48 +02:00
|
|
|
|
if (current_item) {
|
|
|
|
|
|
current_item->submenu = menu;
|
|
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "no menu with id '%s'", id);
|
|
|
|
|
|
}
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
error:
|
2023-10-09 20:46:23 +01:00
|
|
|
|
free(label);
|
|
|
|
|
|
free(execute);
|
|
|
|
|
|
free(id);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-22 21:07:25 +01:00
|
|
|
|
/* This can be one of <separator> and <separator label=""> */
|
|
|
|
|
|
static void
|
|
|
|
|
|
handle_separator_element(xmlNode *n)
|
|
|
|
|
|
{
|
|
|
|
|
|
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
|
|
|
|
|
|
current_item = separator_create(current_menu, label);
|
2023-10-09 20:46:23 +01:00
|
|
|
|
free(label);
|
2022-06-22 21:07:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-17 20:38:16 +00:00
|
|
|
|
static void
|
2021-11-02 18:31:19 +00:00
|
|
|
|
xml_tree_walk(xmlNode *node, struct server *server)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
for (xmlNode *n = node; n && n->name; n = n->next) {
|
|
|
|
|
|
if (!strcasecmp((char *)n->name, "comment")) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
if (!strcasecmp((char *)n->name, "menu")) {
|
|
|
|
|
|
handle_menu_element(n, server);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2022-06-22 21:07:25 +01:00
|
|
|
|
if (!strcasecmp((char *)n->name, "separator")) {
|
|
|
|
|
|
handle_separator_element(n);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2021-02-17 20:38:16 +00:00
|
|
|
|
if (!strcasecmp((char *)n->name, "item")) {
|
2024-06-13 21:00:44 +01:00
|
|
|
|
if (!current_menu) {
|
|
|
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
|
"ignoring <item> without parent <menu>");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2021-02-17 20:38:16 +00:00
|
|
|
|
in_item = true;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
traverse(n, server);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
in_item = false;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
traverse(n, server);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
static bool
|
|
|
|
|
|
parse_buf(struct server *server, struct buf *buf)
|
|
|
|
|
|
{
|
2024-12-08 17:21:11 +00:00
|
|
|
|
int options = 0;
|
|
|
|
|
|
xmlDoc *d = xmlReadMemory(buf->data, buf->len, NULL, NULL, options);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (!d) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "xmlParseMemory()");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
xml_tree_walk(xmlDocGetRootElement(d), server);
|
|
|
|
|
|
xmlFreeDoc(d);
|
|
|
|
|
|
xmlCleanupParser();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-09 20:43:43 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* @stream can come from either of the following:
|
|
|
|
|
|
* - fopen() in the case of reading a file such as menu.xml
|
|
|
|
|
|
* - popen() when processing pipemenus
|
|
|
|
|
|
*/
|
2021-02-17 20:38:16 +00:00
|
|
|
|
static void
|
2023-10-31 21:36:45 +00:00
|
|
|
|
parse_stream(struct server *server, FILE *stream)
|
2021-02-17 20:38:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
char *line = NULL;
|
|
|
|
|
|
size_t len = 0;
|
2024-04-14 14:20:57 -04:00
|
|
|
|
struct buf b = BUF_INIT;
|
2021-02-17 20:38:16 +00:00
|
|
|
|
|
|
|
|
|
|
while (getline(&line, &len, stream) != -1) {
|
|
|
|
|
|
char *p = strrchr(line, '\n');
|
2022-01-09 05:48:27 +01:00
|
|
|
|
if (p) {
|
2021-02-17 20:38:16 +00:00
|
|
|
|
*p = '\0';
|
2022-01-09 05:48:27 +01:00
|
|
|
|
}
|
2021-02-17 20:38:16 +00:00
|
|
|
|
buf_add(&b, line);
|
|
|
|
|
|
}
|
|
|
|
|
|
free(line);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
parse_buf(server, &b);
|
2024-04-14 14:20:57 -04:00
|
|
|
|
buf_reset(&b);
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-09 20:43:43 +01:00
|
|
|
|
static void
|
|
|
|
|
|
parse_xml(const char *filename, struct server *server)
|
|
|
|
|
|
{
|
config: support merging multiple config files
Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.
For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).
When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.
XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.
ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.
This change affects the following config/theme files:
- rc.xml
- menu.xml
- autostart
- environment
- themerc
- themerc-override
- Theme buttons, for example max.xbm
Instead of caching global config/theme directories, create lists of paths
(e.g. '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.
Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.
Avoid using stat() in most situations and just go straight to fopen().
Fixes #1406
2024-01-09 22:00:45 +00:00
|
|
|
|
struct wl_list paths;
|
|
|
|
|
|
paths_config_create(&paths, filename);
|
|
|
|
|
|
|
|
|
|
|
|
bool should_merge_config = rc.merge_config;
|
|
|
|
|
|
struct wl_list *(*iter)(struct wl_list *list);
|
|
|
|
|
|
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
|
|
|
|
|
|
|
|
|
|
|
for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
|
|
|
|
|
|
struct path *path = wl_container_of(elm, path, link);
|
|
|
|
|
|
FILE *stream = fopen(path->string, "r");
|
|
|
|
|
|
if (!stream) {
|
2024-07-08 11:04:57 -04:00
|
|
|
|
continue;
|
config: support merging multiple config files
Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.
For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).
When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.
XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.
ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.
This change affects the following config/theme files:
- rc.xml
- menu.xml
- autostart
- environment
- themerc
- themerc-override
- Theme buttons, for example max.xbm
Instead of caching global config/theme directories, create lists of paths
(e.g. '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.
Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.
Avoid using stat() in most situations and just go straight to fopen().
Fixes #1406
2024-01-09 22:00:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
wlr_log(WLR_INFO, "read menu file %s", path->string);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
parse_stream(server, stream);
|
config: support merging multiple config files
Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.
For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).
When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.
XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.
ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.
This change affects the following config/theme files:
- rc.xml
- menu.xml
- autostart
- environment
- themerc
- themerc-override
- Theme buttons, for example max.xbm
Instead of caching global config/theme directories, create lists of paths
(e.g. '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.
Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.
Avoid using stat() in most situations and just go straight to fopen().
Fixes #1406
2024-01-09 22:00:45 +00:00
|
|
|
|
fclose(stream);
|
|
|
|
|
|
if (!should_merge_config) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2023-10-09 20:43:43 +01:00
|
|
|
|
}
|
config: support merging multiple config files
Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.
For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).
When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.
XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.
ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.
This change affects the following config/theme files:
- rc.xml
- menu.xml
- autostart
- environment
- themerc
- themerc-override
- Theme buttons, for example max.xbm
Instead of caching global config/theme directories, create lists of paths
(e.g. '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.
Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.
Avoid using stat() in most situations and just go straight to fopen().
Fixes #1406
2024-01-09 22:00:45 +00:00
|
|
|
|
paths_destroy(&paths);
|
2023-10-09 20:43:43 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-26 06:42:08 +09:00
|
|
|
|
/*
|
|
|
|
|
|
* Returns the box of a menuitem next to which its submenu is opened.
|
|
|
|
|
|
* This box can be shrunk or expanded by menu overlaps and borders.
|
2024-06-28 18:27:43 +01:00
|
|
|
|
*/
|
2023-10-31 21:13:41 +00:00
|
|
|
|
static struct wlr_box
|
2024-11-26 06:42:08 +09:00
|
|
|
|
get_item_anchor_rect(struct theme *theme, struct menuitem *item)
|
2023-10-31 21:13:41 +00:00
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = item->parent;
|
2024-11-26 06:42:08 +09:00
|
|
|
|
int menu_x = menu->scene_tree->node.x;
|
|
|
|
|
|
int menu_y = menu->scene_tree->node.y;
|
|
|
|
|
|
int overlap_x = theme->menu_overlap_x + theme->menu_border_width;
|
|
|
|
|
|
int overlap_y = theme->menu_overlap_y - theme->menu_border_width;
|
|
|
|
|
|
return (struct wlr_box) {
|
|
|
|
|
|
.x = menu_x + overlap_x,
|
|
|
|
|
|
.y = menu_y + item->tree->node.y + overlap_y,
|
|
|
|
|
|
.width = menu->size.width - 2 * overlap_x,
|
|
|
|
|
|
.height = theme->menu_item_height - 2 * overlap_y,
|
|
|
|
|
|
};
|
2023-10-31 21:13:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
static void
|
2024-11-26 06:42:08 +09:00
|
|
|
|
menu_configure(struct menu *menu, struct wlr_box anchor_rect)
|
2020-10-19 22:14:17 +01:00
|
|
|
|
{
|
2021-11-08 17:36:39 +00:00
|
|
|
|
struct theme *theme = menu->server->theme;
|
|
|
|
|
|
|
2024-11-26 06:42:08 +09:00
|
|
|
|
/* Get output usable area to place the menu within */
|
|
|
|
|
|
struct output *output = output_nearest_to(menu->server,
|
|
|
|
|
|
anchor_rect.x, anchor_rect.y);
|
2023-02-08 23:40:59 -05:00
|
|
|
|
if (!output) {
|
2024-11-26 06:42:08 +09:00
|
|
|
|
wlr_log(WLR_ERROR, "no output found around (%d,%d)",
|
|
|
|
|
|
anchor_rect.x, anchor_rect.y);
|
2022-03-16 05:51:36 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-11-26 06:42:08 +09:00
|
|
|
|
struct wlr_box usable = output_usable_area_in_layout_coords(output);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
|
2024-11-26 06:42:08 +09:00
|
|
|
|
/* Policy for menu placement */
|
|
|
|
|
|
struct wlr_xdg_positioner_rules rules = {0};
|
|
|
|
|
|
rules.size.width = menu->size.width;
|
|
|
|
|
|
rules.size.height = menu->size.height;
|
|
|
|
|
|
/* A rectangle next to which the menu is opened */
|
|
|
|
|
|
rules.anchor_rect = anchor_rect;
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Place menu at left or right side of anchor_rect, with their
|
|
|
|
|
|
* top edges aligned. The alignment is inherited from parent.
|
|
|
|
|
|
*/
|
|
|
|
|
|
if (menu->parent && menu->parent->align_left) {
|
|
|
|
|
|
rules.anchor = XDG_POSITIONER_ANCHOR_TOP_LEFT;
|
|
|
|
|
|
rules.gravity = XDG_POSITIONER_GRAVITY_BOTTOM_LEFT;
|
2022-02-19 02:05:38 +01:00
|
|
|
|
} else {
|
2024-11-26 06:42:08 +09:00
|
|
|
|
rules.anchor = XDG_POSITIONER_ANCHOR_TOP_RIGHT;
|
|
|
|
|
|
rules.gravity = XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT;
|
2022-02-19 02:05:38 +01:00
|
|
|
|
}
|
2024-11-26 06:42:08 +09:00
|
|
|
|
/* Flip or slide the menu when it overflows from the output */
|
|
|
|
|
|
rules.constraint_adjustment =
|
|
|
|
|
|
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X
|
|
|
|
|
|
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
|
|
|
|
|
|
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
|
|
|
|
|
|
if (!menu->parent) {
|
|
|
|
|
|
/* Allow vertically flipping the root menu */
|
|
|
|
|
|
rules.constraint_adjustment |=
|
|
|
|
|
|
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-26 06:42:08 +09:00
|
|
|
|
struct wlr_box box;
|
|
|
|
|
|
wlr_xdg_positioner_rules_get_geometry(&rules, &box);
|
|
|
|
|
|
wlr_xdg_positioner_rules_unconstrain_box(&rules, &usable, &box);
|
|
|
|
|
|
wlr_scene_node_set_position(&menu->scene_tree->node, box.x, box.y);
|
|
|
|
|
|
|
|
|
|
|
|
menu->align_left = (box.x < anchor_rect.x);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
2022-02-19 02:05:38 +01:00
|
|
|
|
struct menuitem *item;
|
2023-08-30 02:37:08 +02:00
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (!item->submenu) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2024-11-26 06:42:08 +09:00
|
|
|
|
anchor_rect = get_item_anchor_rect(theme, item);
|
|
|
|
|
|
menu_configure(item->submenu, anchor_rect);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:49:55 +02:00
|
|
|
|
static void
|
2023-10-09 20:59:04 +01:00
|
|
|
|
menu_hide_submenu(struct server *server, const char *id)
|
2022-06-15 01:49:55 +02:00
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu, *hide_menu;
|
2023-10-09 20:59:04 +01:00
|
|
|
|
hide_menu = menu_get_by_id(server, id);
|
2022-06-15 01:49:55 +02:00
|
|
|
|
if (!hide_menu) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2023-10-09 20:59:04 +01:00
|
|
|
|
wl_list_for_each(menu, &server->menus, link) {
|
2022-06-22 21:07:25 +01:00
|
|
|
|
struct menuitem *item, *next;
|
2023-08-30 02:37:08 +02:00
|
|
|
|
wl_list_for_each_safe(item, next, &menu->menuitems, link) {
|
2022-06-15 01:49:55 +02:00
|
|
|
|
if (item->submenu == hide_menu) {
|
|
|
|
|
|
item_destroy(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-10 18:54:01 +09:00
|
|
|
|
}
|
2022-06-15 01:49:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-18 19:01:28 -05:00
|
|
|
|
static void
|
|
|
|
|
|
init_client_send_to_menu(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Just create placeholder. Contents will be created when launched */
|
|
|
|
|
|
menu_create(server, "client-send-to-menu", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* This is client-send-to-menu
|
|
|
|
|
|
* an internal menu similar to root-menu and client-menu
|
|
|
|
|
|
*
|
|
|
|
|
|
* This will look at workspaces and produce a menu
|
|
|
|
|
|
* with the workspace names that can be used with
|
|
|
|
|
|
* SendToDesktop, left/right options are included.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
update_client_send_to_menu(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = menu_get_by_id(server,
|
|
|
|
|
|
"client-send-to-menu");
|
|
|
|
|
|
|
|
|
|
|
|
if (menu) {
|
|
|
|
|
|
struct menuitem *item, *next;
|
|
|
|
|
|
wl_list_for_each_safe(item, next, &menu->menuitems, link) {
|
|
|
|
|
|
item_destroy(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu->size.height = 0;
|
|
|
|
|
|
|
|
|
|
|
|
struct workspace *workspace;
|
|
|
|
|
|
|
2024-10-01 21:41:36 +01:00
|
|
|
|
wl_list_for_each(workspace, &server->workspaces.all, link) {
|
|
|
|
|
|
if (workspace == server->workspaces.current) {
|
2024-10-20 17:44:32 +09:00
|
|
|
|
char *label = strdup_printf(">%s<", workspace->name);
|
|
|
|
|
|
current_item = item_create(menu, label,
|
|
|
|
|
|
/*show arrow*/ false);
|
|
|
|
|
|
free(label);
|
2024-09-18 19:01:28 -05:00
|
|
|
|
} else {
|
|
|
|
|
|
current_item = item_create(menu, workspace->name, /*show arrow*/ false);
|
|
|
|
|
|
}
|
|
|
|
|
|
fill_item("name.action", "SendToDesktop");
|
|
|
|
|
|
fill_item("to.action", workspace->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-10 18:54:01 +09:00
|
|
|
|
menu_update_scene(menu);
|
2024-09-18 19:01:28 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-17 06:52:10 -05:00
|
|
|
|
static void
|
|
|
|
|
|
init_client_list_combined_menu(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Just create placeholder. Contents will be created when launched */
|
|
|
|
|
|
menu_create(server, "client-list-combined-menu", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* This is client-list-combined-menu an internal menu similar to root-menu and
|
|
|
|
|
|
* client-menu.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This will look at workspaces and produce a menu with the workspace name as a
|
|
|
|
|
|
* separator label and the titles of the view, if any, below each workspace
|
|
|
|
|
|
* name. Active view is indicated by "*" preceeding title.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
update_client_list_combined_menu(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = menu_get_by_id(server, "client-list-combined-menu");
|
|
|
|
|
|
|
|
|
|
|
|
if (!menu) {
|
|
|
|
|
|
/* Menu is created on compositor startup/reconfigure */
|
|
|
|
|
|
wlr_log(WLR_ERROR, "client-list-combined-menu does not exist");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct menuitem *item, *next;
|
|
|
|
|
|
wl_list_for_each_safe(item, next, &menu->menuitems, link) {
|
|
|
|
|
|
item_destroy(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu->size.height = 0;
|
|
|
|
|
|
|
|
|
|
|
|
struct workspace *workspace;
|
|
|
|
|
|
struct view *view;
|
|
|
|
|
|
struct buf buffer = BUF_INIT;
|
|
|
|
|
|
|
2024-10-01 21:41:36 +01:00
|
|
|
|
wl_list_for_each(workspace, &server->workspaces.all, link) {
|
|
|
|
|
|
buf_add_fmt(&buffer, workspace == server->workspaces.current ? ">%s<" : "%s",
|
2024-09-17 06:52:10 -05:00
|
|
|
|
workspace->name);
|
|
|
|
|
|
current_item = separator_create(menu, buffer.data);
|
|
|
|
|
|
buf_clear(&buffer);
|
|
|
|
|
|
|
|
|
|
|
|
wl_list_for_each(view, &server->views, link) {
|
|
|
|
|
|
if (view->workspace == workspace) {
|
2024-09-19 14:53:12 -05:00
|
|
|
|
const char *title = view_get_string_prop(view, "title");
|
2024-08-12 20:31:52 +02:00
|
|
|
|
if (!view->foreign_toplevel || string_null_or_empty(title)) {
|
2024-09-19 14:53:12 -05:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-17 06:52:10 -05:00
|
|
|
|
if (view == server->active_view) {
|
|
|
|
|
|
buf_add(&buffer, "*");
|
|
|
|
|
|
}
|
2024-09-19 14:53:12 -05:00
|
|
|
|
buf_add(&buffer, title);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
|
|
|
|
|
|
current_item = item_create(menu, buffer.data, /*show arrow*/ false);
|
|
|
|
|
|
current_item->id = xstrdup(menu->id);
|
|
|
|
|
|
current_item->client_list_view = view;
|
|
|
|
|
|
fill_item("name.action", "Focus");
|
|
|
|
|
|
fill_item("name.action", "Raise");
|
|
|
|
|
|
buf_clear(&buffer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
current_item = item_create(menu, _("Go there..."), /*show arrow*/ false);
|
|
|
|
|
|
current_item->id = xstrdup(menu->id);
|
|
|
|
|
|
fill_item("name.action", "GoToDesktop");
|
|
|
|
|
|
fill_item("to.action", workspace->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
buf_reset(&buffer);
|
2024-11-10 18:54:01 +09:00
|
|
|
|
menu_update_scene(menu);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 11:54:55 +01:00
|
|
|
|
static void
|
|
|
|
|
|
init_rootmenu(struct server *server)
|
2021-11-02 18:31:19 +00:00
|
|
|
|
{
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu = menu_get_by_id(server, "root-menu");
|
2021-02-19 23:05:14 +00:00
|
|
|
|
|
2021-02-17 20:38:16 +00:00
|
|
|
|
/* Default menu if no menu.xml found */
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (!menu) {
|
2022-01-26 00:07:10 +01:00
|
|
|
|
current_menu = NULL;
|
2022-02-19 02:05:38 +01:00
|
|
|
|
menu = menu_create(server, "root-menu", "");
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (wl_list_empty(&menu->menuitems)) {
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Reconfigure"), false);
|
2022-01-05 09:11:24 +01:00
|
|
|
|
fill_item("name.action", "Reconfigure");
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Exit"), false);
|
2022-01-05 09:11:24 +01:00
|
|
|
|
fill_item("name.action", "Exit");
|
2021-02-17 20:38:16 +00:00
|
|
|
|
}
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 11:54:55 +01:00
|
|
|
|
static void
|
|
|
|
|
|
init_windowmenu(struct server *server)
|
2022-01-26 00:07:10 +01:00
|
|
|
|
{
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu = menu_get_by_id(server, "client-menu");
|
2022-01-26 00:07:10 +01:00
|
|
|
|
|
|
|
|
|
|
/* Default menu if no menu.xml found */
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (!menu) {
|
2022-01-26 00:07:10 +01:00
|
|
|
|
current_menu = NULL;
|
2022-02-19 02:05:38 +01:00
|
|
|
|
menu = menu_create(server, "client-menu", "");
|
2022-01-26 00:07:10 +01:00
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (wl_list_empty(&menu->menuitems)) {
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Minimize"), false);
|
2022-01-26 00:07:10 +01:00
|
|
|
|
fill_item("name.action", "Iconify");
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Maximize"), false);
|
2022-01-26 00:07:10 +01:00
|
|
|
|
fill_item("name.action", "ToggleMaximize");
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Fullscreen"), false);
|
2022-01-26 00:07:10 +01:00
|
|
|
|
fill_item("name.action", "ToggleFullscreen");
|
2024-06-14 22:17:03 +01:00
|
|
|
|
current_item = item_create(menu, _("Roll Up/Down"), false);
|
2023-08-08 03:39:35 +02:00
|
|
|
|
fill_item("name.action", "ToggleShade");
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Decorations"), false);
|
2022-01-26 00:07:10 +01:00
|
|
|
|
fill_item("name.action", "ToggleDecorations");
|
2023-12-19 21:24:55 +00:00
|
|
|
|
current_item = item_create(menu, _("Always on Top"), false);
|
2022-04-09 01:16:09 +02:00
|
|
|
|
fill_item("name.action", "ToggleAlwaysOnTop");
|
2022-06-15 02:02:15 +02:00
|
|
|
|
|
|
|
|
|
|
/* Workspace sub-menu */
|
|
|
|
|
|
struct menu *workspace_menu = menu_create(server, "workspaces", "");
|
2024-06-14 22:17:03 +01:00
|
|
|
|
current_item = item_create(workspace_menu, _("Move Left"), false);
|
2023-03-26 10:57:53 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* <action name="SendToDesktop"><follow> is true by default so
|
|
|
|
|
|
* GoToDesktop will be called as part of the action.
|
|
|
|
|
|
*/
|
2022-06-15 02:02:15 +02:00
|
|
|
|
fill_item("name.action", "SendToDesktop");
|
|
|
|
|
|
fill_item("to.action", "left");
|
2024-06-14 22:17:03 +01:00
|
|
|
|
current_item = item_create(workspace_menu, _("Move Right"), false);
|
2022-06-15 02:02:15 +02:00
|
|
|
|
fill_item("name.action", "SendToDesktop");
|
|
|
|
|
|
fill_item("to.action", "right");
|
2023-12-19 21:24:55 +00:00
|
|
|
|
current_item = separator_create(workspace_menu, "");
|
|
|
|
|
|
current_item = item_create(workspace_menu,
|
|
|
|
|
|
_("Always on Visible Workspace"), false);
|
|
|
|
|
|
fill_item("name.action", "ToggleOmnipresent");
|
|
|
|
|
|
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Workspace"), true);
|
2022-06-15 02:02:15 +02:00
|
|
|
|
current_item->submenu = workspace_menu;
|
|
|
|
|
|
|
2022-08-02 22:00:24 +01:00
|
|
|
|
current_item = item_create(menu, _("Close"), false);
|
2022-01-26 00:07:10 +01:00
|
|
|
|
fill_item("name.action", "Close");
|
|
|
|
|
|
}
|
2022-06-15 01:49:55 +02:00
|
|
|
|
|
|
|
|
|
|
if (wl_list_length(&rc.workspace_config.workspaces) == 1) {
|
2023-10-09 20:59:04 +01:00
|
|
|
|
menu_hide_submenu(server, "workspaces");
|
2022-06-15 01:49:55 +02:00
|
|
|
|
}
|
2022-01-26 00:07:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-06 11:54:55 +01:00
|
|
|
|
void
|
|
|
|
|
|
menu_init(struct server *server)
|
|
|
|
|
|
{
|
2023-10-09 20:59:04 +01:00
|
|
|
|
wl_list_init(&server->menus);
|
2022-12-08 20:41:38 +00:00
|
|
|
|
parse_xml("menu.xml", server);
|
2022-12-06 11:54:55 +01:00
|
|
|
|
init_rootmenu(server);
|
|
|
|
|
|
init_windowmenu(server);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
init_client_list_combined_menu(server);
|
2024-09-18 19:01:28 -05:00
|
|
|
|
init_client_send_to_menu(server);
|
2022-12-05 14:38:16 +01:00
|
|
|
|
post_processing(server);
|
2023-05-13 16:27:46 +02:00
|
|
|
|
validate(server);
|
2022-12-06 11:54:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
static void
|
|
|
|
|
|
nullify_item_pointing_to_this_menu(struct menu *menu)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *iter;
|
|
|
|
|
|
wl_list_for_each(iter, &menu->server->menus, link) {
|
|
|
|
|
|
struct menuitem *item;
|
|
|
|
|
|
wl_list_for_each(item, &iter->menuitems, link) {
|
|
|
|
|
|
if (item->submenu == menu) {
|
|
|
|
|
|
item->submenu = NULL;
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Let's not return early here in case we have
|
|
|
|
|
|
* multiple items pointing to the same menu.
|
|
|
|
|
|
*/
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* This is important for pipe-menus */
|
|
|
|
|
|
if (iter->parent == menu) {
|
|
|
|
|
|
iter->parent = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
menu_free(struct menu *menu)
|
2020-10-22 19:43:27 +01:00
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/* Keep items clean on pipemenu destruction */
|
|
|
|
|
|
nullify_item_pointing_to_this_menu(menu);
|
|
|
|
|
|
|
2024-11-13 23:57:42 +09:00
|
|
|
|
if (menu->server->menu_current == menu) {
|
|
|
|
|
|
menu_close_root(menu->server);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
struct menuitem *item, *next;
|
|
|
|
|
|
wl_list_for_each_safe(item, next, &menu->menuitems, link) {
|
|
|
|
|
|
item_destroy(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Destroying the root node will destroy everything,
|
|
|
|
|
|
* including node descriptors and scaled_font_buffers.
|
|
|
|
|
|
*/
|
2024-11-14 18:16:43 +09:00
|
|
|
|
if (menu->scene_tree) {
|
|
|
|
|
|
wlr_scene_node_destroy(&menu->scene_tree->node);
|
|
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
wl_list_remove(&menu->link);
|
2024-07-08 11:09:20 -04:00
|
|
|
|
zfree(menu->id);
|
|
|
|
|
|
zfree(menu->label);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
zfree(menu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* menu_free_from - free menu list starting from current point
|
|
|
|
|
|
* @from: point to free from (if NULL, all menus are freed)
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void
|
|
|
|
|
|
menu_free_from(struct server *server, struct menu *from)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool destroying = !from;
|
2023-10-09 20:59:04 +01:00
|
|
|
|
struct menu *menu, *tmp_menu;
|
|
|
|
|
|
wl_list_for_each_safe(menu, tmp_menu, &server->menus, link) {
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (menu == from) {
|
|
|
|
|
|
destroying = true;
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (!destroying) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu_free(menu);
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
void
|
|
|
|
|
|
menu_finish(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
menu_free_from(server, NULL);
|
2024-05-05 18:41:10 +09:00
|
|
|
|
|
|
|
|
|
|
/* Reset state vars for starting fresh when Reload is triggered */
|
|
|
|
|
|
current_item = NULL;
|
|
|
|
|
|
current_item_action = NULL;
|
|
|
|
|
|
current_menu = NULL;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-23 20:47:50 +01:00
|
|
|
|
void
|
|
|
|
|
|
menu_on_view_destroy(struct view *view)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct server *server = view->server;
|
|
|
|
|
|
|
|
|
|
|
|
/* If the view being destroy has an open window menu, then close it */
|
|
|
|
|
|
if (server->menu_current
|
|
|
|
|
|
&& server->menu_current->triggered_by_view == view) {
|
|
|
|
|
|
menu_close_root(server);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* TODO: Instead of just setting client_list_view to NULL and deleting
|
|
|
|
|
|
* the actions (as below), consider destroying the item and somehow
|
|
|
|
|
|
* updating the menu and its selection state.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/* Also nullify the destroyed view in client-list-combined-menu */
|
|
|
|
|
|
struct menu *menu = menu_get_by_id(server, "client-list-combined-menu");
|
|
|
|
|
|
if (menu) {
|
|
|
|
|
|
struct menuitem *item;
|
|
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
|
|
|
|
|
if (item->client_list_view == view) {
|
|
|
|
|
|
item->client_list_view = NULL;
|
|
|
|
|
|
action_list_free(&item->actions);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-19 02:05:38 +01:00
|
|
|
|
/* Sets selection (or clears selection if passing NULL) */
|
|
|
|
|
|
static void
|
|
|
|
|
|
menu_set_selection(struct menu *menu, struct menuitem *item)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Clear old selection */
|
|
|
|
|
|
if (menu->selection.item) {
|
|
|
|
|
|
wlr_scene_node_set_enabled(
|
2024-12-03 15:58:07 +09:00
|
|
|
|
&menu->selection.item->normal_tree->node, true);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
wlr_scene_node_set_enabled(
|
2024-12-03 15:58:07 +09:00
|
|
|
|
&menu->selection.item->selected_tree->node, false);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
/* Set new selection */
|
|
|
|
|
|
if (item) {
|
2024-12-03 15:58:07 +09:00
|
|
|
|
wlr_scene_node_set_enabled(&item->normal_tree->node, false);
|
|
|
|
|
|
wlr_scene_node_set_enabled(&item->selected_tree->node, true);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
menu->selection.item = item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
static void
|
|
|
|
|
|
close_all_submenus(struct menu *menu)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menuitem *item;
|
2022-11-03 19:58:21 +00:00
|
|
|
|
wl_list_for_each(item, &menu->menuitems, link) {
|
2021-11-02 18:31:19 +00:00
|
|
|
|
if (item->submenu) {
|
2022-04-04 20:53:36 +01:00
|
|
|
|
wlr_scene_node_set_enabled(
|
|
|
|
|
|
&item->submenu->scene_tree->node, false);
|
2021-11-02 18:31:19 +00:00
|
|
|
|
close_all_submenus(item->submenu);
|
|
|
|
|
|
}
|
2020-10-22 19:43:27 +01:00
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
menu->selection.menu = NULL;
|
2020-10-22 19:43:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* We only destroy pipemenus when closing the entire menu-tree so that pipemenu
|
|
|
|
|
|
* are cached (for as long as the menu is open). This drastically improves the
|
|
|
|
|
|
* felt performance when interacting with multiple pipe menus where a single
|
|
|
|
|
|
* item may be selected multiple times.
|
|
|
|
|
|
*/
|
2022-06-09 17:10:36 +02:00
|
|
|
|
static void
|
2023-10-31 21:36:45 +00:00
|
|
|
|
destroy_pipemenus(struct server *server)
|
2022-06-09 17:10:36 +02:00
|
|
|
|
{
|
2023-10-31 21:36:45 +00:00
|
|
|
|
wlr_log(WLR_DEBUG, "number of menus before close=%d",
|
|
|
|
|
|
wl_list_length(&server->menus));
|
|
|
|
|
|
|
|
|
|
|
|
struct menu *iter, *tmp;
|
|
|
|
|
|
wl_list_for_each_safe(iter, tmp, &server->menus, link) {
|
|
|
|
|
|
if (iter->is_pipemenu) {
|
|
|
|
|
|
menu_free(iter);
|
|
|
|
|
|
}
|
2022-06-09 17:10:36 +02:00
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "number of menus after close=%d",
|
|
|
|
|
|
wl_list_length(&server->menus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
_close(struct menu *menu)
|
|
|
|
|
|
{
|
2022-06-09 17:10:36 +02:00
|
|
|
|
wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
|
|
|
|
|
|
menu_set_selection(menu, NULL);
|
|
|
|
|
|
if (menu->selection.menu) {
|
2023-10-31 21:36:45 +00:00
|
|
|
|
_close(menu->selection.menu);
|
2022-06-09 17:10:36 +02:00
|
|
|
|
menu->selection.menu = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
static void
|
|
|
|
|
|
menu_close(struct menu *menu)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!menu) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Trying to close non exiting menu");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
_close(menu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-19 22:14:17 +01:00
|
|
|
|
void
|
2023-10-31 21:08:04 +00:00
|
|
|
|
menu_open_root(struct menu *menu, int x, int y)
|
2020-10-19 22:14:17 +01:00
|
|
|
|
{
|
2024-12-28 17:03:26 +09:00
|
|
|
|
if (menu->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-02 18:31:19 +00:00
|
|
|
|
assert(menu);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
if (menu->server->menu_current) {
|
|
|
|
|
|
menu_close(menu->server->menu_current);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
destroy_pipemenus(menu->server);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
}
|
2021-11-02 18:31:19 +00:00
|
|
|
|
close_all_submenus(menu);
|
2022-02-19 02:05:38 +01:00
|
|
|
|
menu_set_selection(menu, NULL);
|
2024-11-26 06:42:08 +09:00
|
|
|
|
menu_configure(menu, (struct wlr_box){.x = x, .y = y});
|
2022-02-19 02:05:38 +01:00
|
|
|
|
wlr_scene_node_set_enabled(&menu->scene_tree->node, true);
|
|
|
|
|
|
menu->server->menu_current = menu;
|
|
|
|
|
|
menu->server->input_mode = LAB_INPUT_STATE_MENU;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
selected_item = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2024-08-20 18:09:17 +02:00
|
|
|
|
create_pipe_menu(struct menu_pipe_context *ctx)
|
2023-10-31 21:36:45 +00:00
|
|
|
|
{
|
2024-08-20 18:09:17 +02:00
|
|
|
|
assert(ctx->item);
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
struct menu *pipe_parent = ctx->item->parent;
|
|
|
|
|
|
if (!pipe_parent) {
|
2024-08-20 18:09:17 +02:00
|
|
|
|
wlr_log(WLR_INFO, "[pipemenu %ld] invalid parent",
|
2023-10-31 21:36:45 +00:00
|
|
|
|
(long)ctx->pid);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!pipe_parent->scene_tree->node.enabled) {
|
2024-08-20 18:09:17 +02:00
|
|
|
|
wlr_log(WLR_INFO, "[pipemenu %ld] parent menu already closed",
|
2023-10-31 21:36:45 +00:00
|
|
|
|
(long)ctx->pid);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Pipemenus do not contain a toplevel <menu> element so we have to
|
|
|
|
|
|
* create that first `struct menu`.
|
|
|
|
|
|
*/
|
|
|
|
|
|
struct menu *pipe_menu = menu_create(ctx->server, ctx->item->id, /*label*/ NULL);
|
|
|
|
|
|
pipe_menu->is_pipemenu = true;
|
|
|
|
|
|
pipe_menu->triggered_by_view = pipe_parent->triggered_by_view;
|
|
|
|
|
|
pipe_menu->parent = pipe_parent;
|
|
|
|
|
|
|
|
|
|
|
|
menu_level++;
|
|
|
|
|
|
current_menu = pipe_menu;
|
|
|
|
|
|
if (!parse_buf(ctx->server, &ctx->buf)) {
|
|
|
|
|
|
menu_free(pipe_menu);
|
|
|
|
|
|
ctx->item->submenu = NULL;
|
|
|
|
|
|
goto restore_menus;
|
|
|
|
|
|
}
|
|
|
|
|
|
ctx->item->submenu = pipe_menu;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* TODO: refactor validate() and post_processing() to only
|
|
|
|
|
|
* operate from current point onwards
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/* Set menu-widths before configuring */
|
|
|
|
|
|
post_processing(ctx->server);
|
|
|
|
|
|
|
2024-11-26 06:42:08 +09:00
|
|
|
|
struct wlr_box anchor_rect =
|
|
|
|
|
|
get_item_anchor_rect(ctx->server->theme, ctx->item);
|
|
|
|
|
|
menu_configure(pipe_menu, anchor_rect);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
|
|
|
|
|
validate(ctx->server);
|
|
|
|
|
|
|
|
|
|
|
|
/* Finally open the new submenu tree */
|
|
|
|
|
|
wlr_scene_node_set_enabled(&pipe_menu->scene_tree->node, true);
|
|
|
|
|
|
pipe_parent->selection.menu = pipe_menu;
|
|
|
|
|
|
|
|
|
|
|
|
restore_menus:
|
|
|
|
|
|
current_menu = pipe_parent;
|
|
|
|
|
|
menu_level--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2024-08-20 18:09:17 +02:00
|
|
|
|
pipemenu_ctx_destroy(struct menu_pipe_context *ctx)
|
2023-10-31 21:36:45 +00:00
|
|
|
|
{
|
|
|
|
|
|
wl_event_source_remove(ctx->event_read);
|
|
|
|
|
|
wl_event_source_remove(ctx->event_timeout);
|
|
|
|
|
|
spawn_piped_close(ctx->pid, ctx->pipe_fd);
|
2024-04-14 14:20:57 -04:00
|
|
|
|
buf_reset(&ctx->buf);
|
2024-08-20 18:09:17 +02:00
|
|
|
|
if (ctx->item) {
|
|
|
|
|
|
ctx->item->pipe_ctx = NULL;
|
|
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
free(ctx);
|
|
|
|
|
|
waiting_for_pipe_menu = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
|
handle_pipemenu_timeout(void *_ctx)
|
|
|
|
|
|
{
|
2024-08-20 18:09:17 +02:00
|
|
|
|
struct menu_pipe_context *ctx = _ctx;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
wlr_log(WLR_ERROR, "[pipemenu %ld] timeout reached, killing %s",
|
2024-08-20 18:09:17 +02:00
|
|
|
|
(long)ctx->pid, ctx->item ? ctx->item->execute : "n/a");
|
2023-10-31 21:36:45 +00:00
|
|
|
|
kill(ctx->pid, SIGTERM);
|
|
|
|
|
|
pipemenu_ctx_destroy(ctx);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
|
handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx)
|
|
|
|
|
|
{
|
2024-08-20 18:09:17 +02:00
|
|
|
|
struct menu_pipe_context *ctx = _ctx;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/* two 4k pages + 1 NULL byte */
|
|
|
|
|
|
char data[8193];
|
|
|
|
|
|
ssize_t size;
|
|
|
|
|
|
|
2024-08-20 18:09:17 +02:00
|
|
|
|
if (!ctx->item) {
|
|
|
|
|
|
/* parent menu item got destroyed in the meantime */
|
|
|
|
|
|
wlr_log(WLR_INFO, "[pipemenu %ld] parent menu item destroyed",
|
|
|
|
|
|
(long)ctx->pid);
|
|
|
|
|
|
kill(ctx->pid, SIGTERM);
|
|
|
|
|
|
goto clean_up;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
do {
|
|
|
|
|
|
/* leave space for terminating NULL byte */
|
|
|
|
|
|
size = read(fd, data, sizeof(data) - 1);
|
|
|
|
|
|
} while (size == -1 && errno == EINTR);
|
|
|
|
|
|
|
|
|
|
|
|
if (size == -1) {
|
|
|
|
|
|
wlr_log_errno(WLR_ERROR, "[pipemenu %ld] failed to read data (%s)",
|
|
|
|
|
|
(long)ctx->pid, ctx->item->execute);
|
|
|
|
|
|
goto clean_up;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Limit pipemenu buffer to 1 MiB for safety */
|
|
|
|
|
|
if (ctx->buf.len + size > PIPEMENU_MAX_BUF_SIZE) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "[pipemenu %ld] too big (> %d bytes); killing %s",
|
|
|
|
|
|
(long)ctx->pid, PIPEMENU_MAX_BUF_SIZE, ctx->item->execute);
|
|
|
|
|
|
kill(ctx->pid, SIGTERM);
|
|
|
|
|
|
goto clean_up;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "[pipemenu %ld] read %ld bytes of data", (long)ctx->pid, size);
|
|
|
|
|
|
if (size) {
|
|
|
|
|
|
data[size] = '\0';
|
|
|
|
|
|
buf_add(&ctx->buf, data);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Guard against badly formed data such as binary input */
|
2024-08-20 17:59:46 +01:00
|
|
|
|
if (!str_starts_with(ctx->buf.data, '<', " \t\r\n")) {
|
2023-10-31 21:36:45 +00:00
|
|
|
|
wlr_log(WLR_ERROR, "expect xml data to start with '<'; abort pipemenu");
|
|
|
|
|
|
goto clean_up;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
create_pipe_menu(ctx);
|
|
|
|
|
|
|
|
|
|
|
|
clean_up:
|
|
|
|
|
|
pipemenu_ctx_destroy(ctx);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
parse_pipemenu(struct menuitem *item)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!is_unique_id(item->parent->server, item->id)) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "duplicate id '%s'; abort pipemenu", item->id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-20 18:09:17 +02:00
|
|
|
|
if (item->pipe_ctx) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "item already has a pipe context attached");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
int pipe_fd = 0;
|
|
|
|
|
|
pid_t pid = spawn_piped(item->execute, &pipe_fd);
|
|
|
|
|
|
if (pid <= 0) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to spawn pipe menu process %s", item->execute);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
waiting_for_pipe_menu = true;
|
2024-08-20 18:09:17 +02:00
|
|
|
|
struct menu_pipe_context *ctx = znew(*ctx);
|
2023-10-31 21:36:45 +00:00
|
|
|
|
ctx->server = item->parent->server;
|
|
|
|
|
|
ctx->item = item;
|
|
|
|
|
|
ctx->pid = pid;
|
|
|
|
|
|
ctx->pipe_fd = pipe_fd;
|
2024-04-14 14:20:57 -04:00
|
|
|
|
ctx->buf = BUF_INIT;
|
2024-08-20 18:09:17 +02:00
|
|
|
|
item->pipe_ctx = ctx;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
|
|
|
|
|
ctx->event_read = wl_event_loop_add_fd(ctx->server->wl_event_loop,
|
|
|
|
|
|
pipe_fd, WL_EVENT_READABLE, handle_pipemenu_readable, ctx);
|
|
|
|
|
|
|
|
|
|
|
|
ctx->event_timeout = wl_event_loop_add_timer(ctx->server->wl_event_loop,
|
|
|
|
|
|
handle_pipemenu_timeout, ctx);
|
|
|
|
|
|
wl_event_source_timer_update(ctx->event_timeout, PIPEMENU_TIMEOUT_IN_MS);
|
|
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "[pipemenu %ld] executed: %s", (long)ctx->pid, ctx->item->execute);
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
2020-10-19 22:14:17 +01:00
|
|
|
|
|
2023-08-30 11:20:46 +02:00
|
|
|
|
static void
|
|
|
|
|
|
menu_process_item_selection(struct menuitem *item)
|
2021-11-02 18:31:19 +00:00
|
|
|
|
{
|
2022-06-22 21:07:25 +01:00
|
|
|
|
assert(item);
|
|
|
|
|
|
|
2023-10-31 21:21:20 +00:00
|
|
|
|
/* Do not keep selecting the same item */
|
2023-10-31 21:36:45 +00:00
|
|
|
|
if (item == selected_item) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (waiting_for_pipe_menu) {
|
2023-10-31 21:21:20 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
selected_item = item;
|
2023-10-31 21:21:20 +00:00
|
|
|
|
|
2022-06-22 21:07:25 +01:00
|
|
|
|
if (!item->selectable) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/* We are on an item that has new focus */
|
2022-03-03 04:33:33 +01:00
|
|
|
|
menu_set_selection(item->parent, item);
|
|
|
|
|
|
if (item->parent->selection.menu) {
|
|
|
|
|
|
/* Close old submenu tree */
|
|
|
|
|
|
menu_close(item->parent->selection.menu);
|
|
|
|
|
|
}
|
2021-08-09 17:28:39 +01:00
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
/* Pipemenu */
|
|
|
|
|
|
if (item->execute && !item->submenu) {
|
|
|
|
|
|
/* pipemenus are generated async */
|
|
|
|
|
|
parse_pipemenu(item);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-03 04:33:33 +01:00
|
|
|
|
if (item->submenu) {
|
2022-06-09 17:10:36 +02:00
|
|
|
|
/* Sync the triggering view */
|
|
|
|
|
|
item->submenu->triggered_by_view = item->parent->triggered_by_view;
|
2023-08-30 11:20:46 +02:00
|
|
|
|
/* Ensure the submenu has its parent set correctly */
|
|
|
|
|
|
item->submenu->parent = item->parent;
|
2022-06-09 17:10:36 +02:00
|
|
|
|
/* And open the new submenu tree */
|
2022-03-03 04:33:33 +01:00
|
|
|
|
wlr_scene_node_set_enabled(
|
|
|
|
|
|
&item->submenu->scene_tree->node, true);
|
2021-11-02 18:31:19 +00:00
|
|
|
|
}
|
2023-10-31 21:36:45 +00:00
|
|
|
|
|
2022-03-03 04:33:33 +01:00
|
|
|
|
item->parent->selection.menu = item->submenu;
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-30 11:20:46 +02:00
|
|
|
|
/* Get the deepest submenu with active item selection or the root menu itself */
|
|
|
|
|
|
static struct menu *
|
|
|
|
|
|
get_selection_leaf(struct server *server)
|
2020-10-19 22:14:17 +01:00
|
|
|
|
{
|
2023-08-30 11:20:46 +02:00
|
|
|
|
struct menu *menu = server->menu_current;
|
|
|
|
|
|
if (!menu) {
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
2022-02-19 02:05:38 +01:00
|
|
|
|
|
2023-08-30 11:20:46 +02:00
|
|
|
|
while (menu->selection.menu) {
|
|
|
|
|
|
if (!menu->selection.menu->selection.item) {
|
|
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
menu = menu->selection.menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Selects the next or previous sibling of the currently selected item */
|
|
|
|
|
|
static void
|
|
|
|
|
|
menu_item_select(struct server *server, bool forward)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = get_selection_leaf(server);
|
|
|
|
|
|
if (!menu) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct menuitem *item = NULL;
|
|
|
|
|
|
struct menuitem *selection = menu->selection.item;
|
|
|
|
|
|
struct wl_list *start = selection ? &selection->link : &menu->menuitems;
|
|
|
|
|
|
struct wl_list *current = start;
|
|
|
|
|
|
while (!item || !item->selectable) {
|
|
|
|
|
|
current = forward ? current->next : current->prev;
|
|
|
|
|
|
if (current == start) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (current == &menu->menuitems) {
|
|
|
|
|
|
/* Allow wrap around */
|
|
|
|
|
|
item = NULL;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
item = wl_container_of(current, item, link);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu_process_item_selection(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
|
menu_execute_item(struct menuitem *item)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(item);
|
|
|
|
|
|
|
|
|
|
|
|
if (item->submenu || !item->selectable) {
|
|
|
|
|
|
/* We received a click on a separator or item that just opens a submenu */
|
2022-02-19 02:05:38 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2022-03-03 04:33:33 +01:00
|
|
|
|
|
2023-10-09 20:46:23 +01:00
|
|
|
|
/*
|
2022-06-09 17:10:36 +02:00
|
|
|
|
* We close the menu here to provide a faster feedback to the user.
|
|
|
|
|
|
* We do that without resetting the input state so src/cursor.c
|
|
|
|
|
|
* can do its own clean up on the following RELEASE event.
|
|
|
|
|
|
*/
|
2023-12-28 22:44:59 +00:00
|
|
|
|
struct server *server = item->parent->server;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
menu_close(server->menu_current);
|
|
|
|
|
|
server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
|
2023-12-28 22:44:59 +00:00
|
|
|
|
cursor_update_focus(server);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2023-10-31 21:36:45 +00:00
|
|
|
|
* We call the actions after closing the menu so that virtual keyboard
|
2023-12-28 22:44:59 +00:00
|
|
|
|
* input is sent to the focused_surface instead of being absorbed by the
|
|
|
|
|
|
* menu. Consider for example: `wlrctl keyboard type abc`
|
2023-10-31 21:36:45 +00:00
|
|
|
|
*
|
|
|
|
|
|
* We cannot call menu_close_root() directly here because it does both
|
|
|
|
|
|
* menu_close() and destroy_pipemenus() which we have to handle
|
|
|
|
|
|
* before/after action_run() respectively.
|
2023-12-28 22:44:59 +00:00
|
|
|
|
*/
|
2024-09-17 06:52:10 -05:00
|
|
|
|
if (item->id && !strcmp(item->id, "client-list-combined-menu")
|
|
|
|
|
|
&& item->client_list_view) {
|
2024-09-21 01:11:27 +09:00
|
|
|
|
actions_run(item->client_list_view, server, &item->actions, NULL);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
} else {
|
|
|
|
|
|
actions_run(item->parent->triggered_by_view, server,
|
2024-09-21 01:11:27 +09:00
|
|
|
|
&item->actions, NULL);
|
2024-09-17 06:52:10 -05:00
|
|
|
|
}
|
2023-12-28 22:44:59 +00:00
|
|
|
|
|
2023-10-31 21:36:45 +00:00
|
|
|
|
server->menu_current = NULL;
|
|
|
|
|
|
destroy_pipemenus(server);
|
2022-03-03 04:33:33 +01:00
|
|
|
|
return true;
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-30 11:20:46 +02:00
|
|
|
|
/* Keyboard based selection */
|
|
|
|
|
|
void
|
|
|
|
|
|
menu_item_select_next(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
menu_item_select(server, /* forward */ true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
menu_item_select_previous(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
menu_item_select(server, /* forward */ false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
|
menu_call_selected_actions(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = get_selection_leaf(server);
|
|
|
|
|
|
if (!menu || !menu->selection.item) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return menu_execute_item(menu->selection.item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Selects the first item on the submenu attached to the current selection */
|
|
|
|
|
|
void
|
|
|
|
|
|
menu_submenu_enter(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = get_selection_leaf(server);
|
|
|
|
|
|
if (!menu || !menu->selection.menu) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct wl_list *start = &menu->selection.menu->menuitems;
|
|
|
|
|
|
struct wl_list *current = start;
|
|
|
|
|
|
struct menuitem *item = NULL;
|
|
|
|
|
|
while (!item || !item->selectable) {
|
|
|
|
|
|
current = current->next;
|
|
|
|
|
|
if (current == start) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
item = wl_container_of(current, item, link);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu_process_item_selection(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Re-selects the selected item on the parent menu of the current selection */
|
|
|
|
|
|
void
|
|
|
|
|
|
menu_submenu_leave(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct menu *menu = get_selection_leaf(server);
|
|
|
|
|
|
if (!menu || !menu->parent || !menu->parent->selection.item) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
menu_process_item_selection(menu->parent->selection.item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Mouse based selection */
|
|
|
|
|
|
void
|
|
|
|
|
|
menu_process_cursor_motion(struct wlr_scene_node *node)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(node && node->data);
|
|
|
|
|
|
struct menuitem *item = node_menuitem_from_node(node);
|
|
|
|
|
|
menu_process_item_selection(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
|
menu_call_actions(struct wlr_scene_node *node)
|
|
|
|
|
|
{
|
|
|
|
|
|
assert(node && node->data);
|
|
|
|
|
|
struct menuitem *item = node_menuitem_from_node(node);
|
|
|
|
|
|
|
|
|
|
|
|
return menu_execute_item(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-19 22:14:17 +01:00
|
|
|
|
void
|
2022-06-09 17:10:36 +02:00
|
|
|
|
menu_close_root(struct server *server)
|
2020-10-19 22:14:17 +01:00
|
|
|
|
{
|
2022-06-09 17:10:36 +02:00
|
|
|
|
assert(server->input_mode == LAB_INPUT_STATE_MENU);
|
|
|
|
|
|
if (server->menu_current) {
|
|
|
|
|
|
menu_close(server->menu_current);
|
|
|
|
|
|
server->menu_current = NULL;
|
2023-10-31 21:36:45 +00:00
|
|
|
|
destroy_pipemenus(server);
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
2022-06-09 17:10:36 +02:00
|
|
|
|
server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
|
2020-10-19 22:14:17 +01:00
|
|
|
|
}
|
2021-02-19 23:05:14 +00:00
|
|
|
|
|
|
|
|
|
|
void
|
2022-02-19 02:05:38 +01:00
|
|
|
|
menu_reconfigure(struct server *server)
|
2021-02-19 23:05:14 +00:00
|
|
|
|
{
|
2023-10-09 20:59:04 +01:00
|
|
|
|
menu_finish(server);
|
2022-03-16 06:08:08 +01:00
|
|
|
|
server->menu_current = NULL;
|
2022-12-06 11:54:55 +01:00
|
|
|
|
menu_init(server);
|
2021-02-19 23:05:14 +00:00
|
|
|
|
}
|