diff --git a/include/action.h b/include/action.h index 44a16fd2..b23a0ec9 100644 --- a/include/action.h +++ b/include/action.h @@ -14,7 +14,7 @@ struct action { struct action *action_create(const char *action_name); void action_list_free(struct wl_list *action_list); -void action(struct view *activator, struct server *server, +void actions_run(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges); #endif diff --git a/include/buffer.h b/include/buffer.h new file mode 100644 index 00000000..af833f00 --- /dev/null +++ b/include/buffer.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Based on wlroots/include/types/wlr_buffer.c + * + * Copyright (c) 2017, 2018 Drew DeVault + * Copyright (c) 2018-2021 Simon Ser, Simon Zeni + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LABWC_BUFFER_H +#define __LABWC_BUFFER_H + +#include +#include + +struct lab_data_buffer { + struct wlr_buffer base; + + cairo_t *cairo; + void *data; + uint32_t format; + size_t stride; + bool free_on_destroy; +}; + +/* Create a buffer which creates a new cairo CAIRO_FORMAT_ARGB32 surface */ +struct lab_data_buffer *buffer_create_cairo(uint32_t width, uint32_t height, + float scale, bool free_on_destroy); + +/* Create a buffer which wraps a given DRM_FORMAT_ARGB8888 pointer */ +struct lab_data_buffer *buffer_create_wrap(void *pixel_data, uint32_t width, + uint32_t height, uint32_t stride, bool free_on_destroy); + +#endif /* __LABWC_BUFFER_H */ diff --git a/include/common/font.h b/include/common/font.h index c7af6848..bcf5acd2 100644 --- a/include/common/font.h +++ b/include/common/font.h @@ -2,9 +2,7 @@ #ifndef __LABWC_FONT_H #define __LABWC_FONT_H -struct server; -struct wlr_texture; -struct wlr_box; +struct lab_data_buffer; struct font { char *name; @@ -18,16 +16,23 @@ struct font { int font_height(struct font *font); /** - * texture_create - Create ARGB8888 texture using pango - * @server: context (for wlr_renderer) - * @texture: texture pointer; existing pointer will be freed + * font_buffer_create - Create ARGB8888 lab_data_buffer using pango + * @buffer: buffer pointer * @max_width: max allowable width; will be ellipsized if longer * @text: text to be generated as texture * @font: font description * @color: foreground color in rgba format */ -void font_texture_create(struct server *server, struct wlr_texture **texture, - int max_width, const char *text, struct font *font, float *color); +void font_buffer_create(struct lab_data_buffer **buffer, int max_width, + const char *text, struct font *font, float *color); + +/** + * font_buffer_update - Wrapper around font_buffer_create + * Only difference is that if given buffer pointer is != NULL + * wlr_buffer_drop() will be called on the buffer. + */ +void font_buffer_update(struct lab_data_buffer **buffer, int max_width, + const char *text, struct font *font, float *color); /** * font_finish - free some font related resources diff --git a/include/common/scene-helpers.h b/include/common/scene-helpers.h new file mode 100644 index 00000000..a3aa032b --- /dev/null +++ b/include/common/scene-helpers.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include + +struct wlr_scene_rect *lab_wlr_scene_get_rect(struct wlr_scene_node *node); diff --git a/include/config/mousebind.h b/include/config/mousebind.h index e2da203c..ae66532a 100644 --- a/include/config/mousebind.h +++ b/include/config/mousebind.h @@ -2,8 +2,9 @@ #ifndef __LABWC_MOUSEBIND_H #define __LABWC_MOUSEBIND_H -#include "ssd.h" #include +#include "ssd.h" +#include "config/keybind.h" enum mouse_event { MOUSE_ACTION_NONE = 0, diff --git a/include/debug.h b/include/debug.h new file mode 100644 index 00000000..5b4b4e0c --- /dev/null +++ b/include/debug.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __DEBUG_H +#define __DEBUG_H + +struct server; + +void debug_dump_scene(struct server *server); + +#endif /* __DEBUG_H */ diff --git a/include/labwc.h b/include/labwc.h index 2747aa9e..ee5d72d0 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -24,15 +25,16 @@ #include #include #include -#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -111,6 +113,8 @@ struct seat { struct wl_listener idle_inhibitor_create; }; +struct lab_data_buffer; + struct server { struct wl_display *wl_display; struct wlr_renderer *renderer; @@ -137,6 +141,7 @@ struct server { struct wl_list unmanaged_surfaces; struct seat seat; + struct wlr_scene *scene; /* cursor interactive */ enum input_mode input_mode; @@ -145,6 +150,21 @@ struct server { struct wlr_box grab_box; uint32_t resize_edges; + /* SSD state */ + struct view *ssd_focused_view; + struct ssd_hover_state ssd_hover_state; + + /* Tree for all non-layer xdg/xwayland-shell surfaces */ + struct wlr_scene_tree *view_tree; +#if HAVE_XWAYLAND + /* Tree for unmanaged xsurfaces without initialized view (usually popups) */ + struct wlr_scene_tree *unmanaged_tree; +#endif + /* Tree for built in menu */ + struct wlr_scene_tree *menu_tree; + /* Tree for built in OSD / app switcher */ + struct wlr_scene_tree *osd_tree; + struct wl_list outputs; struct wl_listener new_output; struct wlr_output_layout *output_layout; @@ -164,23 +184,27 @@ struct server { struct view *cycle_view; struct theme *theme; - struct menu *rootmenu; - struct menu *windowmenu; + + struct menu *menu_current; }; +#define LAB_NR_LAYERS (4) struct output { struct wl_list link; /* server::outputs */ struct server *server; struct wlr_output *wlr_output; - struct wlr_output_damage *damage; - struct wl_list layers[4]; + struct wlr_scene_output *scene_output; + struct wl_list layers[LAB_NR_LAYERS]; + struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS]; + struct wlr_scene_tree *layer_popup_tree; struct wlr_box usable_area; - struct wlr_texture *osd; + + struct lab_data_buffer *osd_buffer; struct wl_listener destroy; - struct wl_listener damage_frame; - struct wl_listener damage_destroy; + struct wl_listener frame; }; +#undef LAB_NR_LAYERS enum view_type { LAB_XDG_SHELL_VIEW, @@ -192,10 +216,6 @@ enum view_type { struct view_impl { void (*configure)(struct view *view, struct wlr_box geo); void (*close)(struct view *view); - void (*for_each_popup_surface)(struct view *view, - wlr_surface_iterator_func_t iterator, void *data); - void (*for_each_surface)(struct view *view, - wlr_surface_iterator_func_t iterator, void *data); const char *(*get_string_prop)(struct view *view, const char *prop); void (*map)(struct view *view); void (*move)(struct view *view, double x, double y); @@ -226,6 +246,8 @@ struct view { #endif }; struct wlr_surface *surface; + struct wlr_scene_tree *scene_tree; + struct wlr_scene_node *scene_node; bool mapped; bool been_mapped; @@ -255,24 +277,14 @@ struct view { */ struct border padding; - struct { + struct view_pending_move_resize { bool update_x, update_y; double x, y; uint32_t width, height; uint32_t configure_serial; } pending_move_resize; - struct { - bool enabled; - struct wl_list parts; - struct wlr_box box; /* remember geo so we know when to update */ - } ssd; - - /* The title is unique to each view, so we store these here */ - struct { - struct wlr_texture *active; - struct wlr_texture *inactive; - } title; + struct ssd ssd; struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; struct wl_listener toplevel_handle_request_maximize; @@ -296,7 +308,6 @@ struct view { struct wl_listener set_app_id; /* class on xwayland */ struct wl_listener set_decorations; /* xwayland only */ struct wl_listener new_popup; /* xdg-shell only */ - struct wl_listener new_subsurface; /* xdg-shell only */ }; #if HAVE_XWAYLAND @@ -314,29 +325,6 @@ struct xwayland_unmanaged { }; #endif -struct view_child { - struct wlr_surface *surface; - struct view *parent; - struct wl_listener commit; - struct wl_listener new_subsurface; -}; - -struct view_subsurface { - struct view_child view_child; - struct wlr_subsurface *subsurface; - struct wl_listener destroy; -}; - -struct xdg_popup { - struct view_child view_child; - struct wlr_xdg_popup *wlr_popup; - - struct wl_listener destroy; - struct wl_listener map; - struct wl_listener unmap; - struct wl_listener new_popup; -}; - struct constraint { struct seat *seat; struct wlr_pointer_constraint_v1 *constraint; @@ -361,12 +349,6 @@ void xwayland_unmanaged_create(struct server *server, struct wlr_xwayland_surface *xsurface); #endif -void view_child_init(struct view_child *child, struct view *view, - struct wlr_surface *wlr_surface); -void view_child_finish(struct view_child *child); -void view_subsurface_create(struct view *view, - struct wlr_subsurface *wlr_subsurface); - void view_set_activated(struct view *view, bool activated); void view_close(struct view *view); struct border view_border(struct view *view); @@ -393,10 +375,6 @@ void view_toggle_decorations(struct view *view); void view_set_decorations(struct view *view, bool decorations); void view_toggle_fullscreen(struct view *view); void view_adjust_for_layout_change(struct view *view); -void view_for_each_surface(struct view *view, - wlr_surface_iterator_func_t iterator, void *user_data); -void view_for_each_popup_surface(struct view *view, - wlr_surface_iterator_func_t iterator, void *data); void view_discover_output(struct view *view); void view_move_to_edge(struct view *view, const char *direction); void view_snap_to_edge(struct view *view, const char *direction); @@ -444,11 +422,33 @@ void desktop_focus_topmost_mapped_view(struct server *server); bool isfocusable(struct view *view); /** - * desktop_surface_and_view_at - find view and surface at (lx, ly) - * Note: If surface points to layer-surface, view will be set to NULL + * desktop_node_and_view_at - find view and scene_node at (lx, ly) + * + * Behavior if node points to a surface: + * - If surface is a layer-surface, *view_area will be + * set to LAB_SSD_LAYER_SURFACE and view will be NULL. + * + * - If surface is a 'lost' unmanaged xsurface (one + * with a never-mapped parent view), *view_area will + * be set to LAB_SSD_UNMANAGED and view will be NULL. + * + * 'Lost' unmanaged xsurfaces are usually caused by + * X11 applications opening popups without setting + * the main window as parent. Example: VLC submenus. + * + * - Any other surface will cause *view_area be set to + * LAB_SSD_CLIENT and return the attached view. + * + * Behavior if node points to internal elements: + * - *view_area will be set to the appropiate enum value + * and view will be NULL if the node is not part of the SSD. + * + * If no node is found for the given layout coordinates, + * *view_area will be set to LAB_SSD_ROOT and view will be NULL. + * */ -struct view *desktop_surface_and_view_at(struct server *server, double lx, - double ly, struct wlr_surface **surface, double *sx, double *sy, +struct view *desktop_node_and_view_at(struct server *server, double lx, + double ly, struct wlr_scene_node **scene_node, double *sx, double *sy, enum ssd_part_type *view_area); struct view *desktop_view_at_cursor(struct server *server); @@ -473,8 +473,6 @@ void cursor_finish(struct seat *seat); void keyboard_init(struct seat *seat); void keyboard_finish(struct seat *seat); -void arrange_layers(struct output *output); - void seat_init(struct server *server); void seat_finish(struct server *server); void seat_reconfigure(struct server *server); @@ -486,24 +484,18 @@ void interactive_begin(struct view *view, enum input_mode mode, void interactive_end(struct view *view); void output_init(struct server *server); -void output_damage_surface(struct output *output, struct wlr_surface *surface, - double lx, double ly, bool whole); -void scale_box(struct wlr_box *box, float scale); void output_manager_init(struct server *server); struct output *output_from_wlr_output(struct server *server, struct wlr_output *wlr_output); struct wlr_box output_usable_area_in_layout_coords(struct output *output); struct wlr_box output_usable_area_from_cursor_coords(struct server *server); -void damage_all_outputs(struct server *server); -void damage_view_whole(struct view *view); -void damage_view_part(struct view *view); - void server_init(struct server *server); void server_start(struct server *server); void server_finish(struct server *server); -/* update onscreen display 'alt-tab' texture */ +/* update onscreen display 'alt-tab' buffer */ +void osd_finish(struct server *server); void osd_update(struct server *server); /* diff --git a/include/layers.h b/include/layers.h index 35b41090..b40c0cac 100644 --- a/include/layers.h +++ b/include/layers.h @@ -2,19 +2,14 @@ #ifndef __LABWC_LAYERS_H #define __LABWC_LAYERS_H #include -#include #include struct server; - -enum layer_parent { - LAYER_PARENT_LAYER, - LAYER_PARENT_POPUP, -}; +struct output; struct lab_layer_surface { - struct wlr_layer_surface_v1 *layer_surface; - struct wl_list link; + struct wl_list link; /* output::layers */ + struct wlr_scene_layer_surface_v1 *scene_layer_surface; struct wl_listener destroy; struct wl_listener map; @@ -22,38 +17,26 @@ struct lab_layer_surface { struct wl_listener surface_commit; struct wl_listener output_destroy; struct wl_listener new_popup; - struct wl_listener new_subsurface; struct wlr_box geo; bool mapped; - /* TODO: add extent and layer */ + /* TODO: add extent? */ struct server *server; }; struct lab_layer_popup { struct wlr_xdg_popup *wlr_popup; - enum layer_parent parent_type; - union { - struct lab_layer_surface *parent_layer; - struct lab_layer_popup *parent_popup; - }; - struct wl_listener map; - struct wl_listener unmap; + struct wlr_scene_node *scene_node; + + /* To simplify moving popup nodes from the bottom to the top layer */ + struct wlr_box output_toplevel_sx_box; + struct wl_listener destroy; - struct wl_listener commit; struct wl_listener new_popup; }; -struct lab_layer_subsurface { - struct wlr_subsurface *wlr_subsurface; - struct lab_layer_surface *layer_surface; - - struct wl_listener map; - struct wl_listener unmap; - struct wl_listener destroy; - struct wl_listener commit; -}; - void layers_init(struct server *server); +void layers_arrange(struct output *output); + #endif /* __LABWC_LAYERS_H */ diff --git a/include/menu/menu.h b/include/menu/menu.h index 9cdc8c6f..777bc6af 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -3,19 +3,30 @@ #define __LABWC_MENU_H #include -#include + +struct lab_data_buffer; +struct wlr_scene_node; + +enum menu_align { + LAB_MENU_OPEN_AUTO = 0, + LAB_MENU_OPEN_LEFT = 1 << 0, + LAB_MENU_OPEN_RIGHT = 1 << 1, + LAB_MENU_OPEN_TOP = 1 << 2, + LAB_MENU_OPEN_BOTTOM = 1 << 3, +}; + +struct menu_scene { + struct lab_data_buffer *buffer; + struct wlr_scene_node *text; + struct wlr_scene_node *background; +}; struct menuitem { struct wl_list actions; + struct menu *parent; struct menu *submenu; - struct wlr_box box; - struct { - struct wlr_texture *active; - struct wlr_texture *inactive; - int offset_x; - int offset_y; - } texture; - bool selected; + struct menu_scene normal; + struct menu_scene selected; struct wl_list link; /* menu::menuitems */ }; @@ -23,27 +34,66 @@ struct menuitem { struct menu { char *id; char *label; - bool visible; + int item_height; struct menu *parent; - struct wlr_box box; + struct { + int width; + int height; + } size; struct wl_list menuitems; struct server *server; + struct { + struct menu *menu; + struct menuitem *item; + } selection; + struct wlr_scene_tree *scene_tree; }; void menu_init_rootmenu(struct server *server); void menu_init_windowmenu(struct server *server); void menu_finish(void); -/* menu_move - move to position (x, y) */ -void menu_move(struct menu *menu, int x, int y); +/** + * menu_get_by_id - get menu by id + * + * @id id string defined in menu.xml like "root-menu" + */ +struct menu *menu_get_by_id(const char *id); -/* menu_set_selected - select item at (x, y) */ -void menu_set_selected(struct menu *menu, int x, int y); +/** + * menu_open - open menu on position (x, y) + * + * This function will close server->menu_current, open the + * new menu and assign @menu to server->menu_current. + * + * Additionally, server->input_mode wil be set to LAB_INPUT_STATE_MENU. + */ +void menu_open(struct menu *menu, int x, int y); -/* menu_action_selected - select item at (x, y) */ -void menu_action_selected(struct server *server, struct menu *menu); +/** + * menu_process_cursor_motion + * + * - handles hover effects + * - may open/close submenus + */ +void menu_process_cursor_motion(struct wlr_scene_node *node); + +/** + * menu_call_actions - call actions associated with a menu node + * + * If menuitem connected to @node does not just open a submenu: + * - associated actions will be called + * - server->menu_current will be closed + * - server->menu_current will be set to NULL + * + * Returns true if actions have actually been executed + */ +bool menu_call_actions(struct wlr_scene_node *node); + +/* menu_close - close menu */ +void menu_close(struct menu *menu); /* menu_reconfigure - reload theme and content */ -void menu_reconfigure(struct server *server, struct menu *menu); +void menu_reconfigure(struct server *server); #endif /* __LABWC_MENU_H */ diff --git a/include/node.h b/include/node.h new file mode 100644 index 00000000..5704761c --- /dev/null +++ b/include/node.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __LABWC_NODE_DESCRIPTOR_H +#define __LABWC_NODE_DESCRIPTOR_H +#include + +struct view; +struct lab_layer_surface; +struct lab_layer_popup; +struct menuitem; + +enum node_descriptor_type { + LAB_NODE_DESC_NODE = 0, + LAB_NODE_DESC_VIEW, + LAB_NODE_DESC_XDG_POPUP, + LAB_NODE_DESC_LAYER_SURFACE, + LAB_NODE_DESC_LAYER_POPUP, + LAB_NODE_DESC_MENUITEM, + LAB_NODE_DESC_TREE, +}; + +struct node_descriptor { + enum node_descriptor_type type; + void *data; + struct wl_listener destroy; +}; + +/** + * node_descriptor_create - create node descriptor for wlr_scene_node user_data + * @scene_node: wlr_scene_node to attached node_descriptor to + * @type: node descriptor type + * @data: struct to point to as follows: + * - LAB_NODE_DESC_VIEW struct view + * - LAB_NODE_DESC_XDG_POPUP struct view + * - LAB_NODE_DESC_LAYER_SURFACE struct lab_layer_surface + * - LAB_NODE_DESC_LAYER_POPUP struct lab_layer_popup + */ +void node_descriptor_create(struct wlr_scene_node *scene_node, + enum node_descriptor_type type, void *data); + +/** + * node_view_from_node - return view struct from node + * @wlr_scene_node: wlr_scene_node from which to return data + */ +struct view *node_view_from_node(struct wlr_scene_node *wlr_scene_node); + +/** + * node_lab_surface_from_node - return lab_layer_surface struct from node + * @wlr_scene_node: wlr_scene_node from which to return data + */ +struct lab_layer_surface *node_layer_surface_from_node( + struct wlr_scene_node *wlr_scene_node); + +/** + * node_layer_popup_from_node - return lab_layer_popup struct from node + * @wlr_scene_node: wlr_scene_node from which to return data + */ +struct lab_layer_popup *node_layer_popup_from_node( + struct wlr_scene_node *wlr_scene_node); + +/** + * node_menuitem_from_node - return menuitem struct from node + * @wlr_scene_node: wlr_scene_node from which to return data + */ +struct menuitem *node_menuitem_from_node( + struct wlr_scene_node *wlr_scene_node); + +#endif /* __LABWC_NODE_DESCRIPTOR_H */ diff --git a/include/ssd.h b/include/ssd.h index aeeedf0d..fed6ba0c 100644 --- a/include/ssd.h +++ b/include/ssd.h @@ -2,6 +2,22 @@ #ifndef __LABWC_SSD_H #define __LABWC_SSD_H +#include "buffer.h" +#include + +#define SSD_HEIGHT 26 /* TODO: use theme->title_height */ +#define BUTTON_COUNT 4 +#define BUTTON_WIDTH 26 +#define EXTENDED_AREA 20 + +#define FOR_EACH(tmp, ...) \ +{ \ + __typeof__(tmp) _x[] = { __VA_ARGS__, NULL }; \ + size_t _i = 0; \ + for ((tmp) = _x[_i]; _i < sizeof(_x) / sizeof(_x[0]) - 1; (tmp) = _x[++_i]) + +#define FOR_EACH_END } + /* * Sequence these according to the order they should be processed for * press and hover events. Bear in mind that some of their respective @@ -26,54 +42,144 @@ enum ssd_part_type { LAB_SSD_CLIENT, LAB_SSD_FRAME, LAB_SSD_ROOT, + LAB_SSD_MENU, + LAB_SSD_OSD, + LAB_SSD_LAYER_SURFACE, + LAB_SSD_UNMANAGED, LAB_SSD_END_MARKER }; -/* - * Defer including labwc.h because it is using enum ssd_part_type. - * This is an issue for headers like mousebind.h which only includes - * ssd.h but does not include labwc.h. - */ -#include "labwc.h" +/* Forward declare arguments */ +struct view; +struct wl_list; +struct wlr_box; +struct wlr_scene_tree; + +struct ssd_sub_tree { + struct wlr_scene_tree *tree; + struct wl_list parts; /* ssd_part::link */ +}; + +struct ssd_state_title_width { + int width; + bool truncated; +}; + +struct ssd { + bool enabled; + struct wlr_scene_tree *tree; + + /* + * Cache for current values. + * Used to detect actual changes so we + * don't update things we don't have to. + */ + struct { + int width; + int height; + struct ssd_state_title { + char *text; + struct ssd_state_title_width active; + struct ssd_state_title_width inactive; + } title; + } state; + + /* An invisble area around the view which allows resizing */ + struct ssd_sub_tree extents; + + /* The top of the view, containing buttons, title, .. */ + struct { + /* struct wlr_scene_tree *tree; unused for now */ + struct ssd_sub_tree active; + struct ssd_sub_tree inactive; + } titlebar; + + /* Borders allow resizing as well */ + struct { + /* struct wlr_scene_tree *tree; unused for now */ + struct ssd_sub_tree active; + struct ssd_sub_tree inactive; + } border; +}; struct ssd_part { - struct wlr_box box; enum ssd_part_type type; - /* - * The texture pointers are often held in other places such as the - * theme struct, so here we use ** in order to keep the code - * simple and avoid updating pointers as textures change. - */ - struct { - struct wlr_texture **active; - struct wlr_texture **inactive; - } texture; + /* Buffer pointer. May be NULL */ + struct lab_data_buffer *buffer; - /* - * If a part does not contain textures, it'll just be rendered as a - * rectangle with the following colors. - */ - struct { - float *active; - float *inactive; - } color; + /* This part represented in scene graph */ + struct wlr_scene_node *node; struct wl_list link; }; -struct view; +struct ssd_hover_state { + struct view *view; + enum ssd_part_type type; + struct wlr_scene_node *node; +}; +/* Public SSD API */ +void ssd_create(struct view *view); +void ssd_hide(struct view *view); +void ssd_set_active(struct view *view); +void ssd_update_title(struct view *view); +void ssd_update_geometry(struct view *view); +void ssd_reload(struct view *view); +void ssd_destroy(struct view *view); +/* Returns hover overlay node so it can be disabled later on */ +struct wlr_scene_node *ssd_button_hover_enable( + struct view *view, enum ssd_part_type type); + +/* Public SSD helpers */ +enum ssd_part_type ssd_at(struct view *view, double lx, double ly); +enum ssd_part_type ssd_get_part_type( + struct view *view, struct wlr_scene_node *node); +uint32_t ssd_resize_edges(enum ssd_part_type type); +bool ssd_is_button(enum ssd_part_type type); +bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate); + +/* SSD internal helpers to create various SSD elements */ +/* TODO: Replace some common args with a struct */ +struct ssd_part *add_scene_part( + struct wl_list *part_list, enum ssd_part_type type); +struct ssd_part *add_scene_rect( + struct wl_list *list, enum ssd_part_type type, + struct wlr_scene_node *parent, int width, int height, int x, int y, + float color[4]); +struct ssd_part *add_scene_buffer( + struct wl_list *list, enum ssd_part_type type, + struct wlr_scene_node *parent, struct wlr_buffer *buffer, int x, int y); +struct ssd_part *add_scene_button( + struct wl_list *part_list, enum ssd_part_type type, + struct wlr_scene_node *parent, float *bg_color, + struct wlr_buffer *icon_buffer, int x); +struct ssd_part *add_scene_button_corner( + struct wl_list *part_list, enum ssd_part_type type, + struct wlr_scene_node *parent, struct wlr_buffer *corner_buffer, + struct wlr_buffer *icon_buffer, int x); + +/* SSD internal helpers */ +struct ssd_part *ssd_get_part( + struct wl_list *part_list, enum ssd_part_type type); +void ssd_destroy_parts(struct wl_list *list); + +/* SSD internal */ +void ssd_titlebar_create(struct view *view); +void ssd_titlebar_update(struct view *view); +void ssd_titlebar_destroy(struct view *view); + +void ssd_border_create(struct view *view); +void ssd_border_update(struct view *view); +void ssd_border_destroy(struct view *view); + +void ssd_extents_create(struct view *view); +void ssd_extents_update(struct view *view); +void ssd_extents_destroy(struct view *view); + +/* TODO: clean up / update */ struct border ssd_thickness(struct view *view); struct wlr_box ssd_max_extents(struct view *view); -struct wlr_box ssd_visible_box(struct view *view, enum ssd_part_type type); -enum ssd_part_type ssd_at(struct view *view, double lx, double ly); -uint32_t ssd_resize_edges(enum ssd_part_type type); -void ssd_update_title(struct view *view); -void ssd_create(struct view *view); -void ssd_destroy(struct view *view); -void ssd_update_geometry(struct view *view, bool force); -bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate); -bool ssd_is_button(enum ssd_part_type type); #endif /* __LABWC_SSD_H */ diff --git a/include/theme.h b/include/theme.h index 38aae4c0..a36b9dc1 100644 --- a/include/theme.h +++ b/include/theme.h @@ -54,20 +54,20 @@ struct theme { float osd_label_text_color[4]; /* textures */ - struct wlr_texture *xbm_close_active_unpressed; - struct wlr_texture *xbm_maximize_active_unpressed; - struct wlr_texture *xbm_iconify_active_unpressed; - struct wlr_texture *xbm_menu_active_unpressed; + struct lab_data_buffer *xbm_close_active_unpressed; + struct lab_data_buffer *xbm_maximize_active_unpressed; + struct lab_data_buffer *xbm_iconify_active_unpressed; + struct lab_data_buffer *xbm_menu_active_unpressed; - struct wlr_texture *xbm_close_inactive_unpressed; - struct wlr_texture *xbm_maximize_inactive_unpressed; - struct wlr_texture *xbm_iconify_inactive_unpressed; - struct wlr_texture *xbm_menu_inactive_unpressed; + struct lab_data_buffer *xbm_close_inactive_unpressed; + struct lab_data_buffer *xbm_maximize_inactive_unpressed; + struct lab_data_buffer *xbm_iconify_inactive_unpressed; + struct lab_data_buffer *xbm_menu_inactive_unpressed; - struct wlr_texture *corner_top_left_active_normal; - struct wlr_texture *corner_top_right_active_normal; - struct wlr_texture *corner_top_left_inactive_normal; - struct wlr_texture *corner_top_right_inactive_normal; + struct lab_data_buffer *corner_top_left_active_normal; + struct lab_data_buffer *corner_top_right_active_normal; + struct lab_data_buffer *corner_top_left_inactive_normal; + struct lab_data_buffer *corner_top_right_inactive_normal; /* not set in rc.xml/themerc, but derived from font & padding_height */ int title_height; @@ -76,12 +76,10 @@ struct theme { /** * theme_init - read openbox theme and generate button textures * @theme: theme data - * @renderer: wlr_renderer for creating button textures * @theme_name: theme-name in //openbox-3/themerc * Note is obtained in theme-dir.c */ -void theme_init(struct theme *theme, struct wlr_renderer *renderer, - const char *theme_name); +void theme_init(struct theme *theme, const char *theme_name); /** * theme_finish - free button textures diff --git a/include/xbm/xbm.h b/include/xbm/xbm.h index 739d9a2c..f0957d3d 100644 --- a/include/xbm/xbm.h +++ b/include/xbm/xbm.h @@ -9,6 +9,6 @@ /** * xbm_load - load theme xbm files into global theme struct */ -void xbm_load(struct theme *theme, struct wlr_renderer *renderer); +void xbm_load(struct theme *theme); #endif /* __LABWC_XBM_H */ diff --git a/meson.build b/meson.build index 09f6f0b2..7aea92b2 100644 --- a/meson.build +++ b/meson.build @@ -30,14 +30,14 @@ add_project_arguments(cc.get_supported_arguments( version='"@0@"'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() - git_commit = run_command([git, 'describe', '--dirty']) + git_commit = run_command([git, 'describe', '--dirty'], check: false) if git_commit.returncode() == 0 version = '"@0@"'.format(git_commit.stdout().strip()) endif endif add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') -wlroots_version = ['>=0.15.0', '<0.16.0'] +wlroots_version = ['>=0.16.0', '<0.17.0'] wlroots_proj = subproject( 'wlroots', default_options: ['default_library=static', 'examples=false'], diff --git a/src/action.c b/src/action.c index dfb6ed59..4a283cca 100644 --- a/src/action.c +++ b/src/action.c @@ -3,6 +3,7 @@ #include #include "common/spawn.h" #include "common/zfree.h" +#include "debug.h" #include "labwc.h" #include "menu/menu.h" #include "ssd.h" @@ -78,7 +79,8 @@ action_create(const char *action_name) return action; } -void action_list_free(struct wl_list *action_list) { +void action_list_free(struct wl_list *action_list) +{ struct action *action, *action_tmp; wl_list_for_each_safe(action, action_tmp, action_list, link) { wl_list_remove(&action->link); @@ -90,19 +92,15 @@ void action_list_free(struct wl_list *action_list) { static void show_menu(struct server *server, struct view *view, const char *menu_name) { - struct menu *menu = NULL; bool force_menu_top_left = false; - - if (!menu_name) { + struct menu *menu = menu_get_by_id(menu_name); + if (!menu) { return; } - - if (!strcasecmp(menu_name, "root-menu")) { - menu = server->rootmenu; - server->windowmenu->visible = false; - } else if (!strcasecmp(menu_name, "client-menu") && view) { - menu = server->windowmenu; - server->rootmenu->visible = false; + if (!strcasecmp(menu_name, "client-menu")) { + if (!view) { + return; + } enum ssd_part_type type = ssd_at(view, server->seat.cursor->x, server->seat.cursor->y); if (type == LAB_SSD_BUTTON_WINDOW_MENU) { @@ -112,13 +110,8 @@ show_menu(struct server *server, struct view *view, const char *menu_name) } else { force_menu_top_left = true; } - } else { - return; } - menu->visible = true; - server->input_mode = LAB_INPUT_STATE_MENU; - int x, y; if (force_menu_top_left) { x = view->x; @@ -127,8 +120,7 @@ show_menu(struct server *server, struct view *view, const char *menu_name) x = server->seat.cursor->x; y = server->seat.cursor->y; } - menu_move(menu, x, y); - damage_all_outputs(server); + menu_open(menu, x, y); } static struct view * @@ -138,7 +130,8 @@ activator_or_focused_view(struct view *activator, struct server *server) } void -action(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges) +actions_run(struct view *activator, struct server *server, + struct wl_list *actions, uint32_t resize_edges) { if (!actions) { wlr_log(WLR_ERROR, "empty actions"); @@ -161,7 +154,7 @@ action(struct view *activator, struct server *server, struct wl_list *actions, u } break; case ACTION_TYPE_DEBUG: - /* nothing */ + debug_dump_scene(server); break; case ACTION_TYPE_EXECUTE: { @@ -218,7 +211,6 @@ action(struct view *activator, struct server *server, struct wl_list *actions, u view = desktop_view_at_cursor(server); if (view) { desktop_focus_and_activate_view(&server->seat, view); - damage_all_outputs(server); } break; case ACTION_TYPE_ICONIFY: @@ -235,7 +227,6 @@ action(struct view *activator, struct server *server, struct wl_list *actions, u case ACTION_TYPE_RAISE: if (view) { desktop_move_to_front(view); - damage_all_outputs(server); } break; case ACTION_TYPE_RESIZE: diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 00000000..44891005 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Based on wlroots/types/wlr_buffer.c + * + * Copyright (c) 2017, 2018 Drew DeVault + * Copyright (c) 2018-2021 Simon Ser, Simon Zeni + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" +#include +#include +#include +#include +#include "buffer.h" + +static const struct wlr_buffer_impl data_buffer_impl; + +static struct lab_data_buffer * +data_buffer_from_buffer(struct wlr_buffer *buffer) +{ + assert(buffer->impl == &data_buffer_impl); + return (struct lab_data_buffer *)buffer; +} + +static void +data_buffer_destroy(struct wlr_buffer *wlr_buffer) +{ + struct lab_data_buffer *buffer = data_buffer_from_buffer(wlr_buffer); + if (!buffer->free_on_destroy) { + free(buffer); + return; + } + if (buffer->cairo) { + cairo_surface_t *surf = cairo_get_target(buffer->cairo); + cairo_destroy(buffer->cairo); + cairo_surface_destroy(surf); + } else if (buffer->data) { + free(buffer->data); + buffer->data = NULL; + } + free(buffer); +} + +static bool +data_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + struct lab_data_buffer *buffer = + wl_container_of(wlr_buffer, buffer, base); + assert(buffer->data); + *data = (void *)buffer->data; + *format = buffer->format; + *stride = buffer->stride; + return true; +} + +static void +data_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) +{ + /* noop */ +} + +static const struct wlr_buffer_impl data_buffer_impl = { + .destroy = data_buffer_destroy, + .begin_data_ptr_access = data_buffer_begin_data_ptr_access, + .end_data_ptr_access = data_buffer_end_data_ptr_access, +}; + +struct lab_data_buffer * +buffer_create_cairo(uint32_t width, uint32_t height, float scale, + bool free_on_destroy) +{ + struct lab_data_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + return NULL; + } + wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); + + cairo_surface_t *surf = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_surface_set_device_scale(surf, scale, scale); + + buffer->cairo = cairo_create(surf); + buffer->data = cairo_image_surface_get_data(surf); + buffer->format = DRM_FORMAT_ARGB8888; + buffer->stride = cairo_image_surface_get_stride(surf); + buffer->free_on_destroy = free_on_destroy; + + if (!buffer->data) { + cairo_destroy(buffer->cairo); + cairo_surface_destroy(surf); + free(buffer); + buffer = NULL; + } + return buffer; +} + +struct lab_data_buffer * +buffer_create_wrap(void *pixel_data, uint32_t width, uint32_t height, + uint32_t stride, bool free_on_destroy) +{ + struct lab_data_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + return NULL; + } + wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); + buffer->data = pixel_data; + buffer->format = DRM_FORMAT_ARGB8888; + buffer->stride = stride; + buffer->free_on_destroy = free_on_destroy; + return buffer; +} diff --git a/src/common/font.c b/src/common/font.c index e5222d00..6925a504 100644 --- a/src/common/font.c +++ b/src/common/font.c @@ -7,6 +7,7 @@ #include #include "common/font.h" #include "labwc.h" +#include "buffer.h" static PangoRectangle font_extents(struct font *font, const char *string) @@ -49,25 +50,33 @@ font_height(struct font *font) } void -font_texture_create(struct server *server, struct wlr_texture **texture, - int max_width, const char *text, struct font *font, float *color) +font_buffer_update(struct lab_data_buffer **buffer, int max_width, + const char *text, struct font *font, float *color) +{ + if (*buffer) { + wlr_buffer_drop(&(*buffer)->base); + *buffer = NULL; + } + font_buffer_create(buffer, max_width, text, font, color); +} + +void +font_buffer_create(struct lab_data_buffer **buffer, int max_width, + const char *text, struct font *font, float *color) { if (!text || !*text) { return; } - if (*texture) { - wlr_texture_destroy(*texture); - *texture = NULL; - } PangoRectangle rect = font_extents(font, text); if (max_width && rect.width > max_width) { rect.width = max_width; } + /* TODO: scale */ + *buffer = buffer_create_cairo(rect.width, rect.height, 1, true); - cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - rect.width, rect.height); - cairo_t *cairo = cairo_create(surf); + cairo_t *cairo = (*buffer)->cairo; + cairo_surface_t *surf = cairo_get_target(cairo); cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]); cairo_move_to(cairo, 0, 0); @@ -88,13 +97,6 @@ font_texture_create(struct server *server, struct wlr_texture **texture, g_object_unref(layout); cairo_surface_flush(surf); - unsigned char *data = cairo_image_surface_get_data(surf); - *texture = wlr_texture_from_pixels(server->renderer, DRM_FORMAT_ARGB8888, - cairo_image_surface_get_stride(surf), rect.width, - rect.height, data); - - cairo_destroy(cairo); - cairo_surface_destroy(surf); } void diff --git a/src/common/meson.build b/src/common/meson.build index 02c763aa..fdfa72b2 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -4,6 +4,7 @@ labwc_sources += files( 'font.c', 'grab-file.c', 'nodename.c', + 'scene-helpers.c', 'spawn.c', 'string-helpers.c', 'zfree.c', diff --git a/src/common/scene-helpers.c b/src/common/scene-helpers.c new file mode 100644 index 00000000..a973a691 --- /dev/null +++ b/src/common/scene-helpers.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +struct wlr_scene_rect * +lab_wlr_scene_get_rect(struct wlr_scene_node *node) +{ + assert(node->type == WLR_SCENE_NODE_RECT); + return (struct wlr_scene_rect *)node; +} diff --git a/src/cursor.c b/src/cursor.c index 8e3227c7..7aa32d35 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -12,15 +12,30 @@ #include "ssd.h" #include "config/mousebind.h" +static bool +is_surface(enum ssd_part_type view_area) +{ + return view_area == LAB_SSD_CLIENT + || view_area == LAB_SSD_LAYER_SURFACE +#if HAVE_XWAYLAND + || view_area == LAB_SSD_UNMANAGED +#endif + ; +} + void cursor_rebase(struct seat *seat, uint32_t time_msec) { double sx, sy; - struct wlr_surface *surface; + struct wlr_scene_node *node; enum ssd_part_type view_area = LAB_SSD_NONE; + struct wlr_surface *surface = NULL; - desktop_surface_and_view_at(seat->server, seat->cursor->x, - seat->cursor->y, &surface, &sx, &sy, &view_area); + desktop_node_and_view_at(seat->server, seat->cursor->x, + seat->cursor->y, &node, &sx, &sy, &view_area); + if (is_surface(view_area)) { + surface = wlr_scene_surface_from_node(node)->surface; + } if (surface) { wlr_seat_pointer_notify_clear_focus(seat->seat); @@ -37,7 +52,7 @@ request_cursor_notify(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, request_cursor); /* - * This event is rasied by the seat when a client provides a cursor + * This event is raised by the seat when a client provides a cursor * image */ struct wlr_seat_pointer_request_set_cursor_event *event = data; @@ -98,7 +113,6 @@ request_start_drag_notify(struct wl_listener *listener, void *data) static void process_cursor_move(struct server *server, uint32_t time) { - damage_all_outputs(server); double dx = server->seat.cursor->x - server->grab_x; double dy = server->seat.cursor->y - server->grab_y; struct view *view = server->grabbed_view; @@ -113,7 +127,6 @@ process_cursor_move(struct server *server, uint32_t time) static void process_cursor_resize(struct server *server, uint32_t time) { - damage_all_outputs(server); double dx = server->seat.cursor->x - server->grab_x; double dy = server->seat.cursor->y - server->grab_y; @@ -170,28 +183,10 @@ input_inhibit_blocks_surface(struct seat *seat, struct wl_resource *resource) inhibiting_client != wl_resource_get_client(resource); } -static struct output * -get_output(struct server *server, struct wlr_cursor *cursor) -{ - struct wlr_output *wlr_output = wlr_output_layout_output_at( - server->output_layout, cursor->x, cursor->y); - return output_from_wlr_output(server, wlr_output); -} - -static void -damage_whole_current_output(struct server *server) -{ - struct output *output = get_output(server, server->seat.cursor); - if (output) { - wlr_output_damage_add_whole(output->damage); - } -} - static void process_cursor_motion(struct server *server, uint32_t time) { static bool cursor_name_set_by_server; - static enum ssd_part_type last_button_hover = LAB_SSD_NONE; /* If the mode is non-passthrough, delegate to those functions. */ if (server->input_mode == LAB_INPUT_STATE_MOVE) { @@ -200,53 +195,47 @@ process_cursor_motion(struct server *server, uint32_t time) } else if (server->input_mode == LAB_INPUT_STATE_RESIZE) { process_cursor_resize(server, time); return; - } else if (server->input_mode == LAB_INPUT_STATE_MENU) { - struct menu *menu = NULL; - if (server->rootmenu->visible) { - menu = server->rootmenu; - } else if (server->windowmenu->visible) { - menu = server->windowmenu; - } else { - return; - } - menu_set_selected(menu, - server->seat.cursor->x, server->seat.cursor->y); - damage_all_outputs(server); - return; } /* Otherwise, find view under the pointer and send the event along */ double sx, sy; struct wlr_seat *wlr_seat = server->seat.seat; - struct wlr_surface *surface = NULL; + struct wlr_scene_node *node; enum ssd_part_type view_area = LAB_SSD_NONE; - struct view *view = desktop_surface_and_view_at(server, - server->seat.cursor->x, server->seat.cursor->y, &surface, + struct view *view = desktop_node_and_view_at(server, + server->seat.cursor->x, server->seat.cursor->y, &node, &sx, &sy, &view_area); + struct wlr_surface *surface = NULL; + if (is_surface(view_area)) { + surface = wlr_scene_surface_from_node(node)->surface; + } + /* resize handles */ uint32_t resize_edges = ssd_resize_edges(view_area); /* Set cursor */ - if (!view) { - /* root, etc. */ + if (view_area == LAB_SSD_ROOT || view_area == LAB_SSD_MENU) { cursor_set(&server->seat, XCURSOR_DEFAULT); - } else { - if (resize_edges) { - cursor_name_set_by_server = true; - cursor_set(&server->seat, - wlr_xcursor_get_resize_name(resize_edges)); - } else if (ssd_part_contains(LAB_SSD_PART_TITLEBAR, view_area)) { - /* title and buttons */ - cursor_set(&server->seat, XCURSOR_DEFAULT); - cursor_name_set_by_server = true; - } else if (cursor_name_set_by_server) { - /* window content */ - cursor_set(&server->seat, XCURSOR_DEFAULT); - cursor_name_set_by_server = false; - } + } else if (resize_edges) { + cursor_name_set_by_server = true; + cursor_set(&server->seat, + wlr_xcursor_get_resize_name(resize_edges)); + } else if (ssd_part_contains(LAB_SSD_PART_TITLEBAR, view_area)) { + /* title and buttons */ + cursor_set(&server->seat, XCURSOR_DEFAULT); + cursor_name_set_by_server = true; + } else if (cursor_name_set_by_server) { + /* xdg/xwindow window content */ + /* layershell or unmanaged */ + cursor_set(&server->seat, XCURSOR_DEFAULT); + cursor_name_set_by_server = false; } + if (view_area == LAB_SSD_MENU) { + menu_process_cursor_motion(node); + return; + } if (view && rc.focus_follow_mouse) { desktop_focus_and_activate_view(&server->seat, view); @@ -270,21 +259,28 @@ process_cursor_motion(struct server *server, uint32_t time) } mousebind->pressed_in_context = false; - action(NULL, server, &mousebind->actions, resize_edges); + actions_run(NULL, server, &mousebind->actions, resize_edges); } } - /* Required for iconify/maximize/close button mouse-over deco */ + /* SSD button mouse-over */ + struct ssd_hover_state *hover = &server->ssd_hover_state; if (ssd_is_button(view_area)) { - if (last_button_hover != view_area) { - /* Cursor entered new button area */ - damage_whole_current_output(server); - last_button_hover = view_area; + /* Cursor entered new button area */ + if (hover->view != view || hover->type != view_area) { + if (hover->node) { + wlr_scene_node_set_enabled(hover->node, false); + } + hover->view = view; + hover->type = view_area; + hover->node = ssd_button_hover_enable(view, view_area); } - } else if (last_button_hover != LAB_SSD_NONE) { + } else if (hover->node) { /* Cursor left button area */ - damage_whole_current_output(server); - last_button_hover = LAB_SSD_NONE; + wlr_scene_node_set_enabled(hover->node, false); + hover->view = NULL; + hover->type = LAB_SSD_NONE; + hover->node = NULL; } if (surface && @@ -507,12 +503,19 @@ handle_release_mousebinding(struct view *view, struct server *server, break; } continue; + case MOUSE_ACTION_DRAG: + if (mousebind->pressed_in_context) { + /* Swallow the release event as well as the press one */ + activated_any = true; + activated_any_frame |= mousebind->context == LAB_SSD_FRAME; + } + continue; default: continue; } activated_any = true; activated_any_frame |= mousebind->context == LAB_SSD_FRAME; - action(view, server, &mousebind->actions, resize_edges); + actions_run(view, server, &mousebind->actions, resize_edges); } } /* @@ -576,6 +579,9 @@ handle_press_mousebinding(struct view *view, struct server *server, * counted as a DOUBLECLICK. */ if (!double_click) { + /* Swallow the press event as well as the release one */ + activated_any = true; + activated_any_frame |= mousebind->context == LAB_SSD_FRAME; mousebind->pressed_in_context = true; } continue; @@ -591,7 +597,7 @@ handle_press_mousebinding(struct view *view, struct server *server, } activated_any = true; activated_any_frame |= mousebind->context == LAB_SSD_FRAME; - action(view, server, &mousebind->actions, resize_edges); + actions_run(view, server, &mousebind->actions, resize_edges); } } return activated_any && activated_any_frame; @@ -610,27 +616,48 @@ cursor_button(struct wl_listener *listener, void *data) wlr_idle_notify_activity(seat->wlr_idle, seat->seat); double sx, sy; - struct wlr_surface *surface; + struct wlr_scene_node *node; enum ssd_part_type view_area = LAB_SSD_NONE; - uint32_t resize_edges; + uint32_t resize_edges = 0; + + /** + * Used in WLR_BUTTON_RELEASED, set on WLR_BUTTON_PRESSED + * + * Automatically initialized with 0 / false and + * checkpatch.pl complains when done manually. + */ + static bool close_menu; /* bindings to the Frame context swallow mouse events if activated */ bool triggered_frame_binding = false; - struct view *view = desktop_surface_and_view_at(server, - server->seat.cursor->x, server->seat.cursor->y, &surface, + struct view *view = desktop_node_and_view_at(server, + server->seat.cursor->x, server->seat.cursor->y, &node, &sx, &sy, &view_area); + struct wlr_surface *surface = NULL; + if (is_surface(view_area)) { + surface = wlr_scene_surface_from_node(node)->surface; + } + /* get modifiers */ - struct wlr_input_device *device = seat->keyboard_group->input_device; - uint32_t modifiers = wlr_keyboard_get_modifiers(device->keyboard); + struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; + uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); /* handle _release_ */ if (event->state == WLR_BUTTON_RELEASED) { if (server->input_mode == LAB_INPUT_STATE_MENU) { + if (close_menu) { + if (server->menu_current) { + menu_close(server->menu_current); + server->menu_current = NULL; + } + server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; + cursor_rebase(&server->seat, event->time_msec); + close_menu = false; + } return; } - damage_all_outputs(server); if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { /* Exit interactive move/resize/menu mode. */ if (server->grabbed_view == view) { @@ -640,56 +667,41 @@ cursor_button(struct wl_listener *listener, void *data) server->grabbed_view = NULL; } cursor_rebase(&server->seat, event->time_msec); - } - - /* Handle _release_ on root window */ - if (!view) { - handle_release_mousebinding(NULL, server, event->button, - modifiers, LAB_SSD_ROOT, 0); + return; } goto mousebindings; } + /* Handle _press */ if (server->input_mode == LAB_INPUT_STATE_MENU) { - if (server->rootmenu->visible) { - menu_action_selected(server, server->rootmenu); - } else if (server->windowmenu->visible) { - menu_action_selected(server, server->windowmenu); + if (view_area != LAB_SSD_MENU) { + /* We close the menu on release so we don't leak a stray release */ + close_menu = true; + } else if (menu_call_actions(node)) { + /* Action was successfull, may fail if item just opens a submenu */ + close_menu = true; } - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; - cursor_rebase(&server->seat, event->time_msec); return; } /* Handle _press_ on a layer surface */ - if (!view && surface) { - if (!wlr_surface_is_layer_surface(surface)) { - return; - } + if (view_area == LAB_SSD_LAYER_SURFACE) { struct wlr_layer_surface_v1 *layer = wlr_layer_surface_v1_from_wlr_surface(surface); if (layer->current.keyboard_interactive) { seat_set_focus_layer(&server->seat, layer); } - wlr_seat_pointer_notify_button(seat->seat, event->time_msec, - event->button, event->state); - return; } - /* Handle _press_ on root window */ - if (!view) { - handle_press_mousebinding(NULL, server, - event->button, modifiers, LAB_SSD_ROOT, 0); - return; - } - - /* Determine closest resize edges in case action is Resize */ - resize_edges = ssd_resize_edges(view_area); - if (!resize_edges) { - resize_edges |= server->seat.cursor->x < view->x + view->w / 2 - ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT; - resize_edges |= server->seat.cursor->y < view->y + view->h / 2 - ? WLR_EDGE_TOP : WLR_EDGE_BOTTOM; + if (view) { + /* Determine closest resize edges in case action is Resize */ + resize_edges = ssd_resize_edges(view_area); + if (!resize_edges) { + resize_edges |= server->seat.cursor->x < view->x + view->w / 2 + ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT; + resize_edges |= server->seat.cursor->y < view->y + view->h / 2 + ? WLR_EDGE_TOP : WLR_EDGE_BOTTOM; + } } mousebindings: @@ -702,7 +714,7 @@ mousebindings: server, event->button, modifiers, view_area, resize_edges); } - if (!triggered_frame_binding) { + if (surface && !triggered_frame_binding) { /* Notify client with pointer focus of button press */ wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state); diff --git a/src/damage.c b/src/damage.c deleted file mode 100644 index 3a1e52c6..00000000 --- a/src/damage.c +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include "labwc.h" - -void -damage_all_outputs(struct server *server) -{ - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - if (output && output->wlr_output && output->damage) { - wlr_output_damage_add_whole(output->damage); - } - } -} - -void -damage_view_part(struct view *view) -{ - struct output *output; - wl_list_for_each (output, &view->server->outputs, link) { - output_damage_surface(output, view->surface, view->x, view->y, - false); - } -} - -void -damage_view_whole(struct view *view) -{ - struct output *output; - wl_list_for_each (output, &view->server->outputs, link) { - output_damage_surface(output, view->surface, view->x, view->y, - true); - } -} diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 00000000..68dcbc2a --- /dev/null +++ b/src/debug.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include "buffer.h" +#include "labwc.h" +#include "node.h" + +#define HEADER_CHARS "------------------------------" + +#define INDENT_SIZE 3 +#define IGNORE_SSD true +#define IGNORE_MENU true +#define LEFT_COL_SPACE 35 + +static const char * +get_node_type(enum wlr_scene_node_type type) +{ + switch (type) { + case WLR_SCENE_NODE_ROOT: + return "root"; + case WLR_SCENE_NODE_TREE: + return "tree"; + case WLR_SCENE_NODE_SURFACE: + return "surface"; + case WLR_SCENE_NODE_RECT: + return "rect"; + case WLR_SCENE_NODE_BUFFER: + return "buffer"; + } + return "error"; +} + +static const char * +get_layer_name(uint32_t layer) +{ + switch (layer) { + case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: + return "layer-background"; + case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: + return "layer-bottom"; + case ZWLR_LAYER_SHELL_V1_LAYER_TOP: + return "layer-top"; + case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: + return "layer-overlay"; + default: + abort(); + } +} + +static const char * +get_view_part(struct view *view, struct wlr_scene_node *node) +{ + if (view && node == &view->scene_tree->node) { + return "view"; + } + if (view && node == view->scene_node) { + return "view->scene_node"; + } + if (!view || !view->ssd.tree) { + return NULL; + } + if (node == &view->ssd.tree->node) { + return "view->ssd"; + } + if (node == &view->ssd.titlebar.active.tree->node) { + return "titlebar.active"; + } + if (node == &view->ssd.titlebar.inactive.tree->node) { + return "titlebar.inactive"; + } + if (node == &view->ssd.border.active.tree->node) { + return "border.active"; + } + if (node == &view->ssd.border.inactive.tree->node) { + return "border.inactive"; + } + if (node == &view->ssd.extents.tree->node) { + return "extents"; + } + return NULL; +} + +static const char * +get_special(struct server *server, struct wlr_scene_node *node, + struct view **last_view) +{ + if (node == &server->scene->node) { + return "server->scene"; + } + if (node == &server->osd_tree->node) { + return "server->osd_tree"; + } + if (node == &server->menu_tree->node) { + return "server->menu_tree"; + } + if (node->parent == &server->scene->node) { + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + for (int i = 0; i < 4; i++) { + if (node == &output->layer_tree[i]->node) { + return get_layer_name(i); + } + } + } + } +#if HAVE_XWAYLAND + if (node == &server->unmanaged_tree->node) { + return "server->unmanaged_tree"; + } +#endif + if (node == &server->view_tree->node) { + return "server->view_tree"; + } + if (node->parent == &server->view_tree->node) { + *last_view = node_view_from_node(node); + } + const char *view_part = get_view_part(*last_view, node); + if (view_part) { + return view_part; + } + return get_node_type(node->type); +} + +struct pad { + uint8_t left; + uint8_t right; +}; + +static struct pad +get_center_padding(const char *text, uint8_t max_width) +{ + struct pad pad; + size_t text_len = strlen(text); + pad.left = (double)(max_width - text_len) / 2 + 0.5f; + pad.right = max_width - pad.left - text_len; + return pad; +} + +static void +dump_tree(struct server *server, struct wlr_scene_node *node, + int pos, int x, int y) +{ + static struct view *view; + const char *type = get_special(server, node, &view); + + if (pos) { + printf("%*c+-- ", pos, ' '); + } else { + struct pad node_pad = get_center_padding("Node", 16); + printf(" %*c %4s %4s %*c%s\n", LEFT_COL_SPACE + 4, ' ', + "X", "Y", node_pad.left, ' ', "Node"); + printf(" %*c %.4s %.4s %.16s\n", LEFT_COL_SPACE + 4, ' ', + HEADER_CHARS, HEADER_CHARS, HEADER_CHARS); + printf(" "); + } + int padding = LEFT_COL_SPACE - pos - strlen(type); + if (!pos) { + padding += 3; + } + printf("%s %*c %4d %4d [%p]\n", type, padding, ' ', x, y, node); + + if ((IGNORE_MENU && node == &server->menu_tree->node) + || (IGNORE_SSD && view && view->ssd.tree + && node == &view->ssd.tree->node)) { + printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', ""); + return; + } + struct wlr_scene_node *child; + wl_list_for_each(child, &node->state.children, state.link) { + dump_tree(server, child, pos + INDENT_SIZE, + x + child->state.x, y + child->state.y); + } +} + +void +debug_dump_scene(struct server *server) +{ + printf("\n"); + dump_tree(server, &server->scene->node, 0, 0, 0); + printf("\n"); +} diff --git a/src/desktop.c b/src/desktop.c index 9d80da0f..d8768de5 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -3,6 +3,7 @@ #include #include "labwc.h" #include "layers.h" +#include "node.h" #include "ssd.h" static void @@ -10,6 +11,7 @@ move_to_front(struct view *view) { wl_list_remove(&view->link); wl_list_insert(&view->server->views, &view->link); + wlr_scene_node_raise_to_top(&view->scene_tree->node); } #if HAVE_XWAYLAND @@ -197,7 +199,6 @@ desktop_cycle_view(struct server *server, struct view *current, view = wl_container_of(view->link.prev, view, link); } while (&view->link == &server->views || !isfocusable(view)); } - damage_all_outputs(server); return view; } @@ -255,189 +256,85 @@ desktop_focus_topmost_mapped_view(struct server *server) desktop_move_to_front(view); } -static bool -_view_at(struct view *view, double lx, double ly, struct wlr_surface **surface, - double *sx, double *sy) -{ - /* - * XDG toplevels may have nested surfaces, such as popup windows for - * context menus or tooltips. This function tests if any of those are - * underneath the coordinates lx and ly (in output Layout Coordinates). - * If so, it sets the surface pointer to that wlr_surface and the sx and - * sy coordinates to the coordinates relative to that surface's top-left - * corner. - */ - double view_sx = lx - view->x; - double view_sy = ly - view->y; - double _sx, _sy; - struct wlr_surface *_surface = NULL; - - switch (view->type) { - case LAB_XDG_SHELL_VIEW: - _surface = wlr_xdg_surface_surface_at( - view->xdg_surface, view_sx, view_sy, &_sx, &_sy); - break; -#if HAVE_XWAYLAND - case LAB_XWAYLAND_VIEW: - _surface = wlr_surface_surface_at(view->surface, view_sx, - view_sy, &_sx, &_sy); - break; -#endif - } - - if (_surface) { - *sx = _sx; - *sy = _sy; - *surface = _surface; - return true; - } - return false; -} - -static struct -wlr_surface *layer_surface_at(struct wl_list *layer, double ox, double oy, - double *sx, double *sy) -{ - struct lab_layer_surface *surface; - wl_list_for_each_reverse(surface, layer, link) { - double _sx = ox - surface->geo.x; - double _sy = oy - surface->geo.y; - struct wlr_surface *wlr_surface; - wlr_surface = wlr_layer_surface_v1_surface_at( - surface->layer_surface, _sx, _sy, sx, sy); - if (wlr_surface) { - return wlr_surface; - } - } - return NULL; -} - -static bool -surface_is_xdg_popup(struct wlr_surface *surface) -{ - if (wlr_surface_is_xdg_surface(surface)) { - struct wlr_xdg_surface *xdg_surface = - wlr_xdg_surface_from_wlr_surface(surface); - return xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP; - } - return false; -} - -static struct wlr_surface * -layer_surface_popup_at(struct output *output, struct wl_list *layer, - double ox, double oy, double *sx, double *sy) -{ - struct lab_layer_surface *lab_layer; - wl_list_for_each_reverse(lab_layer, layer, link) { - double _sx = ox - lab_layer->geo.x; - double _sy = oy - lab_layer->geo.y; - struct wlr_surface *sub = wlr_layer_surface_v1_surface_at( - lab_layer->layer_surface, _sx, _sy, sx, sy); - if (sub && surface_is_xdg_popup(sub)) { - return sub; - } - } - return NULL; -} - struct view * -desktop_surface_and_view_at(struct server *server, double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy, +desktop_node_and_view_at(struct server *server, double lx, double ly, + struct wlr_scene_node **scene_node, double *sx, double *sy, enum ssd_part_type *view_area) { - struct wlr_output *wlr_output = wlr_output_layout_output_at( - server->output_layout, lx, ly); - struct output *output = output_from_wlr_output(server, wlr_output); + struct wlr_scene_node *node = + wlr_scene_node_at(&server->scene->node, lx, ly, sx, sy); - if (!output) { + *scene_node = node; + if (!node) { + *view_area = LAB_SSD_ROOT; return NULL; } - - double ox = lx, oy = ly; - wlr_output_layout_output_coords(output->server->output_layout, - wlr_output, &ox, &oy); - - /* Overlay-layer */ - *surface = layer_surface_at( - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - - /* Check all layer popups */ - *surface = layer_surface_popup_at(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - *surface = layer_surface_popup_at(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - *surface = layer_surface_popup_at(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - - /* Top-layer */ - *surface = layer_surface_at( - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - - struct view *view; - wl_list_for_each (view, &server->views, link) { - if (!view->mapped) { - continue; + if (node->type == WLR_SCENE_NODE_SURFACE) { + struct wlr_surface *surface = + wlr_scene_surface_from_node(node)->surface; + if (wlr_surface_is_layer_surface(surface)) { + *view_area = LAB_SSD_LAYER_SURFACE; + return NULL; } - - /* This ignores server-side deco */ - if (_view_at(view, lx, ly, surface, sx, sy)) { - *view_area = LAB_SSD_CLIENT; - return view; +#if HAVE_XWAYLAND + if (node->parent == &server->unmanaged_tree->node) { + *view_area = LAB_SSD_UNMANAGED; + return NULL; } - if (!view->ssd.enabled) { - continue; +#endif + } + struct wlr_scene_node *osd = &server->osd_tree->node; + while (node) { + struct node_descriptor *desc = node->data; + /* TODO: convert to switch() */ + if (desc) { + if (desc->type == LAB_NODE_DESC_VIEW) { + goto has_view_data; + } + if (desc->type == LAB_NODE_DESC_XDG_POPUP) { + goto has_view_data; + } + if (desc->type == LAB_NODE_DESC_LAYER_POPUP) { + /* FIXME: we shouldn't have to set *view_area */ + *view_area = LAB_SSD_CLIENT; + return NULL; + } + if (desc->type == LAB_NODE_DESC_MENUITEM) { + /* Always return the top scene node for menu items */ + *scene_node = node; + *view_area = LAB_SSD_MENU; + return NULL; + } } - - /* Now, let's deal with the server-side deco */ - *view_area = ssd_at(view, lx, ly); - if (*view_area != LAB_SSD_NONE) { - return view; + if (node == osd) { + *view_area = LAB_SSD_OSD; + return NULL; } + node = node->parent; } - - *surface = layer_surface_at( - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - ox, oy, sx, sy); - if (*surface) { - return NULL; - } - *surface = layer_surface_at( - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - ox, oy, sx, sy); - if (*surface) { - return NULL; + if (!node) { + wlr_log(WLR_ERROR, "Unknown node detected"); } + *view_area = LAB_SSD_NONE; return NULL; + +struct view *view; +struct node_descriptor *desc; +has_view_data: + desc = node->data; + view = desc->data; + *view_area = ssd_get_part_type(view, *scene_node); + return view; } struct view * desktop_view_at_cursor(struct server *server) { double sx, sy; - struct wlr_surface *surface; + struct wlr_scene_node *node; enum ssd_part_type view_area = LAB_SSD_NONE; - return desktop_surface_and_view_at(server, + return desktop_node_and_view_at(server, server->seat.cursor->x, server->seat.cursor->y, - &surface, &sx, &sy, &view_area); + &node, &sx, &sy, &view_area); } diff --git a/src/keyboard.c b/src/keyboard.c index 9b1bf846..5f44ce21 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -2,6 +2,7 @@ #include #include #include "action.h" +#include "buffer.h" #include "key-state.h" #include "labwc.h" @@ -40,15 +41,15 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data) if (server->cycle_view) { struct wlr_event_keyboard_key *event = data; - struct wlr_input_device *device = seat->keyboard_group->input_device; - damage_all_outputs(server); + struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; if ((event->state == WL_KEYBOARD_KEY_STATE_RELEASED) - && !any_modifiers_pressed(device->keyboard)) { + && !any_modifiers_pressed(keyboard)) { /* end cycle */ desktop_focus_and_activate_view(&server->seat, server->cycle_view); desktop_move_to_front(server->cycle_view); server->cycle_view = NULL; + osd_finish(server); } } @@ -68,7 +69,7 @@ handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym) for (size_t i = 0; i < keybind->keysyms_len; i++) { if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { wlr_keyboard_set_repeat_info(kb, 0, 0); - action(NULL, server, &keybind->actions, 0); + actions_run(NULL, server, &keybind->actions, 0); return true; } } @@ -94,13 +95,13 @@ handle_compositor_keybindings(struct wl_listener *listener, { struct seat *seat = wl_container_of(listener, seat, keyboard_key); struct server *server = seat->server; - struct wlr_input_device *device = seat->keyboard_group->input_device; + struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; /* Translate libinput keycode -> xkbcommon */ uint32_t keycode = event->keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ const xkb_keysym_t *syms; - int nsyms = xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, &syms); + int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms); bool handled = false; @@ -115,14 +116,13 @@ handle_compositor_keybindings(struct wl_listener *listener, && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { int nr_bound_keys = key_state_bound_key_remove(keycode); if (!nr_bound_keys) { - wlr_keyboard_set_repeat_info(device->keyboard, + wlr_keyboard_set_repeat_info(keyboard, rc.repeat_rate, rc.repeat_delay); } return true; } - uint32_t modifiers = - wlr_keyboard_get_modifiers(device->keyboard); + uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); /* Catch C-A-F1 to C-A-F12 to change tty */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { @@ -137,7 +137,6 @@ handle_compositor_keybindings(struct wl_listener *listener, } if (server->cycle_view) { - damage_all_outputs(server); if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { if (syms[i] == XKB_KEY_Escape) { @@ -188,7 +187,7 @@ keyboard_key_notify(struct wl_listener *listener, void *data) struct server *server = seat->server; struct wlr_event_keyboard_key *event = data; struct wlr_seat *wlr_seat = server->seat.seat; - struct wlr_input_device *device = seat->keyboard_group->input_device; + struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; wlr_idle_notify_activity(seat->wlr_idle, seat->seat); bool handled = false; @@ -199,7 +198,7 @@ keyboard_key_notify(struct wl_listener *listener, void *data) } if (!handled) { - wlr_seat_set_keyboard(wlr_seat, device); + wlr_seat_set_keyboard(wlr_seat, &keyboard->base); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } diff --git a/src/layers.c b/src/layers.c index 1200c95a..2112b48b 100644 --- a/src/layers.c +++ b/src/layers.c @@ -2,7 +2,7 @@ /* * layers.c - layer-shell implementation * - * Based on: + * Based on * - https://git.sr.ht/~sircmpwm/wio * - https://github.com/swaywm/sway * Copyright (C) 2019 Drew DeVault and Sway developers @@ -16,208 +16,54 @@ #include #include "layers.h" #include "labwc.h" - -static void -apply_exclusive(struct wlr_box *usable_area, uint32_t anchor, int32_t exclusive, - int32_t margin_top, int32_t margin_right, int32_t margin_bottom, - int32_t margin_left) -{ - if (exclusive <= 0) { - return; - } - struct { - uint32_t anchors; - int *positive_axis; - int *negative_axis; - int margin; - } edges[] = { - { - .anchors = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, - .positive_axis = &usable_area->y, - .negative_axis = &usable_area->height, - .margin = margin_top, - }, - { - .anchors = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = NULL, - .negative_axis = &usable_area->height, - .margin = margin_bottom, - }, - { - .anchors = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = &usable_area->x, - .negative_axis = &usable_area->width, - .margin = margin_left, - }, - { - .anchors = - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = NULL, - .negative_axis = &usable_area->width, - .margin = margin_right, - }, - }; - for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { - if ((anchor & edges[i].anchors) == edges[i].anchors) { - if (edges[i].positive_axis) { - *edges[i].positive_axis += - exclusive + edges[i].margin; - } - if (edges[i].negative_axis) { - *edges[i].negative_axis -= - exclusive + edges[i].margin; - } - } - } -} - -/** - * @list: struct lab_layer_surface - */ -static void -arrange_layer(struct wlr_output *output, struct wl_list *list, - struct wlr_box *usable_area, bool exclusive) -{ - struct lab_layer_surface *surface; - struct wlr_box full_area = { 0 }; - wlr_output_effective_resolution(output, &full_area.width, - &full_area.height); - wl_list_for_each_reverse(surface, list, link) { - struct wlr_layer_surface_v1 *layer = surface->layer_surface; - struct wlr_layer_surface_v1_state *state = &layer->current; - if (exclusive != (state->exclusive_zone > 0)) { - continue; - } - struct wlr_box bounds; - if (state->exclusive_zone == -1) { - bounds = full_area; - } else { - bounds = *usable_area; - } - struct wlr_box box = { - .width = state->desired_width, - .height = state->desired_height - }; - /* Horizontal axis */ - const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT - | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if ((state->anchor & both_horiz) && box.width == 0) { - box.x = bounds.x; - box.width = bounds.width; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { - box.x = bounds.x; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { - box.x = bounds.x + (bounds.width - box.width); - } else { - box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); - } - /* Vertical axis */ - const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP - | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - if ((state->anchor & both_vert) && box.height == 0) { - box.y = bounds.y; - box.height = bounds.height; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { - box.y = bounds.y; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { - box.y = bounds.y + (bounds.height - box.height); - } else { - box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); - } - /* Margin */ - if ((state->anchor & both_horiz) == both_horiz) { - box.x += state->margin.left; - box.width -= state->margin.left + state->margin.right; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { - box.x += state->margin.left; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { - box.x -= state->margin.right; - } - if ((state->anchor & both_vert) == both_vert) { - box.y += state->margin.top; - box.height -= state->margin.top + state->margin.bottom; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { - box.y += state->margin.top; - } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { - box.y -= state->margin.bottom; - } - if (box.width < 0 || box.height < 0) { - wlr_log(WLR_ERROR, "surface has no positive size"); - continue; - } - - /* Apply */ - surface->geo = box; - apply_exclusive(usable_area, state->anchor, - state->exclusive_zone, state->margin.top, - state->margin.right, state->margin.bottom, - state->margin.left); - wlr_layer_surface_v1_configure(layer, box.width, box.height); - } -} +#include "node.h" void -arrange_layers(struct output *output) +layers_arrange(struct output *output) { - assert(output); - - struct wlr_box usable_area = { 0 }; + struct wlr_box full_area = { 0 }; wlr_output_effective_resolution(output->wlr_output, - &usable_area.width, &usable_area.height); + &full_area.width, &full_area.height); + struct wlr_box usable_area = full_area; + + struct server *server = output->server; + struct wlr_scene_output *scene_output = + wlr_scene_get_scene_output(server->scene, output->wlr_output); + if (!scene_output) { + wlr_log(WLR_DEBUG, "no wlr_scene_output"); + return; + } + + int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]); + for (int i = 0; i < nr_layers; i++) { + struct lab_layer_surface *lab_layer_surface; + wl_list_for_each(lab_layer_surface, &output->layers[i], link) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + lab_layer_surface->scene_layer_surface; + wlr_scene_layer_surface_v1_configure( + scene_layer_surface, &full_area, &usable_area); + } + + wlr_scene_node_set_position(&output->layer_tree[i]->node, + scene_output->x, scene_output->y); + } - /* Exclusive surfaces */ - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, true); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, true); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, true); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, true); memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); - /* TODO: re-arrange all views taking into account updated usable_area */ - - /* Non-exclusive surfaces */ - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - &usable_area, false); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - &usable_area, false); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - &usable_area, false); - arrange_layer(output->wlr_output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - &usable_area, false); - /* Find topmost keyboard interactive layer, if such a layer exists */ uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, }; - size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); + size_t nlayers = sizeof(layers_above_shell) + / sizeof(layers_above_shell[0]); struct lab_layer_surface *layer, *topmost = NULL; for (size_t i = 0; i < nlayers; ++i) { wl_list_for_each_reverse (layer, &output->layers[layers_above_shell[i]], link) { - if (layer->layer_surface->current.keyboard_interactive) { + struct wlr_layer_surface_v1 *layer_surface = + layer->scene_layer_surface->layer_surface; + if (layer_surface->current.keyboard_interactive) { topmost = layer; break; } @@ -228,11 +74,13 @@ arrange_layers(struct output *output) } struct seat *seat = &output->server->seat; if (topmost) { - seat_set_focus_layer(seat, topmost->layer_surface); + seat_set_focus_layer(seat, + topmost->scene_layer_surface->layer_surface); } else if (seat->focused_layer && !seat->focused_layer->current.keyboard_interactive) { seat_set_focus_layer(seat, NULL); } + /* FIXME: should we call a desktop_arrange_all_views() here? */ } static void @@ -240,9 +88,8 @@ output_destroy_notify(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, output_destroy); - layer->layer_surface->output = NULL; wl_list_remove(&layer->output_destroy.link); - wlr_layer_surface_v1_destroy(layer->layer_surface); + wlr_layer_surface_v1_destroy(layer->scene_layer_surface->layer_surface); } static void @@ -250,8 +97,10 @@ surface_commit_notify(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, surface_commit); - struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; - struct wlr_output *wlr_output = layer->layer_surface->output; + struct wlr_layer_surface_v1 *layer_surface = + layer->scene_layer_surface->layer_surface; + struct wlr_output *wlr_output = + layer->scene_layer_surface->layer_surface->output; if (!wlr_output) { return; @@ -262,19 +111,17 @@ surface_commit_notify(struct wl_listener *listener, void *data) layer->mapped = layer_surface->mapped; struct output *output = output_from_wlr_output(layer->server, wlr_output); - arrange_layers(output); + layers_arrange(output); } - damage_all_outputs(layer->server); } static void unmap(struct lab_layer_surface *layer) { struct seat *seat = &layer->server->seat; - if (seat->focused_layer == layer->layer_surface) { + if (seat->focused_layer == layer->scene_layer_surface->layer_surface) { seat_set_focus_layer(seat, NULL); } - damage_all_outputs(layer->server); } static void @@ -282,18 +129,20 @@ destroy_notify(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of( listener, layer, destroy); - if (layer->layer_surface->mapped) { + if (layer->scene_layer_surface->layer_surface->mapped) { unmap(layer); } + wl_list_remove(&layer->link); wl_list_remove(&layer->destroy.link); wl_list_remove(&layer->map.link); + wl_list_remove(&layer->unmap.link); wl_list_remove(&layer->surface_commit.link); - if (layer->layer_surface->output) { + if (layer->scene_layer_surface->layer_surface->output) { wl_list_remove(&layer->output_destroy.link); - struct output *output = output_from_wlr_output( - layer->server, layer->layer_surface->output); - arrange_layers(output); + struct output *output = output_from_wlr_output(layer->server, + layer->scene_layer_surface->layer_surface->output); + layers_arrange(output); } free(layer); } @@ -301,150 +150,18 @@ destroy_notify(struct wl_listener *listener, void *data) static void unmap_notify(struct wl_listener *listener, void *data) { - struct lab_layer_surface *l = wl_container_of(listener, l, unmap); - unmap(l); + return; + struct lab_layer_surface *lab_layer_surface = + wl_container_of(listener, lab_layer_surface, unmap); + unmap(lab_layer_surface); } static void map_notify(struct wl_listener *listener, void *data) { - struct wlr_layer_surface_v1 *l = data; - wlr_surface_send_enter(l->surface, l->output); -} - -static void -subsurface_damage(struct lab_layer_subsurface *subsurface, bool whole) -{ - struct lab_layer_surface *layer = subsurface->layer_surface; - struct wlr_output *wlr_output = layer->layer_surface->output; - if (!wlr_output) { - return; - } -// struct output *output = wlr_output->data; -// int ox = subsurface->wlr_subsurface->current.x + layer->geo.x; -// int oy = subsurface->wlr_subsurface->current.y + layer->geo.y; - damage_all_outputs(layer->server); -} - -static void -subsurface_handle_unmap(struct wl_listener *listener, void *data) -{ - struct lab_layer_subsurface *subsurface = - wl_container_of(listener, subsurface, unmap); - subsurface_damage(subsurface, true); -} - -static void -subsurface_handle_map(struct wl_listener *listener, void *data) -{ - struct lab_layer_subsurface *subsurface = - wl_container_of(listener, subsurface, map); - subsurface_damage(subsurface, true); -} - -static void -subsurface_handle_commit(struct wl_listener *listener, void *data) -{ - struct lab_layer_subsurface *subsurface = - wl_container_of(listener, subsurface, commit); - subsurface_damage(subsurface, false); -} - -static void -subsurface_handle_destroy(struct wl_listener *listener, void *data) -{ - struct lab_layer_subsurface *subsurface = - wl_container_of(listener, subsurface, destroy); - - wl_list_remove(&subsurface->map.link); - wl_list_remove(&subsurface->unmap.link); - wl_list_remove(&subsurface->destroy.link); - wl_list_remove(&subsurface->commit.link); - free(subsurface); -} - -static struct -lab_layer_subsurface *create_subsurface(struct wlr_subsurface *wlr_subsurface, - struct lab_layer_surface *layer_surface) -{ - struct lab_layer_subsurface *subsurface = - calloc(1, sizeof(struct lab_layer_subsurface)); - if (!subsurface) { - return NULL; - } - - subsurface->wlr_subsurface = wlr_subsurface; - subsurface->layer_surface = layer_surface; - - subsurface->map.notify = subsurface_handle_map; - wl_signal_add(&wlr_subsurface->events.map, &subsurface->map); - subsurface->unmap.notify = subsurface_handle_unmap; - wl_signal_add(&wlr_subsurface->events.unmap, &subsurface->unmap); - subsurface->destroy.notify = subsurface_handle_destroy; - wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); - subsurface->commit.notify = subsurface_handle_commit; - wl_signal_add(&wlr_subsurface->surface->events.commit, - &subsurface->commit); - - return subsurface; -} - -static void -new_subsurface_notify(struct wl_listener *listener, void *data) -{ - struct lab_layer_surface *lab_layer_surface = - wl_container_of(listener, lab_layer_surface, new_subsurface); - struct wlr_subsurface *wlr_subsurface = data; - create_subsurface(wlr_subsurface, lab_layer_surface); -} - - -static struct -lab_layer_surface *popup_get_layer(struct lab_layer_popup *popup) -{ - while (popup->parent_type == LAYER_PARENT_POPUP) { - popup = popup->parent_popup; - } - return popup->parent_layer; -} - -static void -popup_damage(struct lab_layer_popup *layer_popup, bool whole) -{ - struct lab_layer_surface *layer; - while (true) { - if (layer_popup->parent_type == LAYER_PARENT_POPUP) { - layer_popup = layer_popup->parent_popup; - } else { - layer = layer_popup->parent_layer; - break; - } - } - damage_all_outputs(layer->server); -} - -static void -popup_handle_map(struct wl_listener *listener, void *data) -{ - struct lab_layer_popup *popup = wl_container_of(listener, popup, map); - struct lab_layer_surface *layer = popup_get_layer(popup); - struct wlr_output *wlr_output = layer->layer_surface->output; - wlr_surface_send_enter(popup->wlr_popup->base->surface, wlr_output); - popup_damage(popup, true); -} - -static void -popup_handle_unmap(struct wl_listener *listener, void *data) -{ - struct lab_layer_popup *popup = wl_container_of(listener, popup, unmap); - popup_damage(popup, true); -} - -static void -popup_handle_commit(struct wl_listener *listener, void *data) -{ - struct lab_layer_popup *popup = wl_container_of(listener, popup, commit); - popup_damage(popup, false); + return; + struct wlr_layer_surface_v1 *layer_surface = data; + wlr_surface_send_enter(layer_surface->surface, layer_surface->output); } static void @@ -452,40 +169,16 @@ popup_handle_destroy(struct wl_listener *listener, void *data) { struct lab_layer_popup *popup = wl_container_of(listener, popup, destroy); - - wl_list_remove(&popup->map.link); - wl_list_remove(&popup->unmap.link); wl_list_remove(&popup->destroy.link); - wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->new_popup.link); free(popup); } -static void -popup_unconstrain(struct lab_layer_popup *popup) -{ - struct lab_layer_surface *layer = popup_get_layer(popup); - struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; - struct output *output = layer->layer_surface->output->data; - - struct wlr_box output_box = { 0 }; - wlr_output_effective_resolution(output->wlr_output, &output_box.width, - &output_box.height); - - struct wlr_box output_toplevel_sx_box = { - .x = -layer->geo.x, - .y = -layer->geo.y, - .width = output_box.width, - .height = output_box.height, - }; - - wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); -} - static void popup_handle_new_popup(struct wl_listener *listener, void *data); static struct lab_layer_popup * -create_popup(struct wlr_xdg_popup *wlr_popup, - enum layer_parent parent_type, void *parent) +create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_node *parent, + struct wlr_box *output_toplevel_sx_box) { struct lab_layer_popup *popup = calloc(1, sizeof(struct lab_layer_popup)); @@ -494,41 +187,98 @@ create_popup(struct wlr_xdg_popup *wlr_popup, } popup->wlr_popup = wlr_popup; - popup->parent_type = parent_type; - popup->parent_layer = parent; + popup->scene_node = + wlr_scene_xdg_surface_create(parent, wlr_popup->base); + if (!popup->scene_node) { + free(popup); + return NULL; + } + node_descriptor_create(popup->scene_node, + LAB_NODE_DESC_LAYER_POPUP, popup); - popup->map.notify = popup_handle_map; - wl_signal_add(&wlr_popup->base->events.map, &popup->map); - popup->unmap.notify = popup_handle_unmap; - wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); - popup->commit.notify = popup_handle_commit; - wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); - popup_unconstrain(popup); - + wlr_xdg_popup_unconstrain_from_box(wlr_popup, output_toplevel_sx_box); return popup; } +/* This popup's parent is a layer popup */ static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct lab_layer_popup *lab_layer_popup = wl_container_of(listener, lab_layer_popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; - create_popup(wlr_popup, LAYER_PARENT_POPUP, lab_layer_popup); + struct lab_layer_popup *new_popup = create_popup(wlr_popup, + lab_layer_popup->scene_node, + &lab_layer_popup->output_toplevel_sx_box); + new_popup->output_toplevel_sx_box = + lab_layer_popup->output_toplevel_sx_box; } +/* + * We move popups from the bottom to the top layer so that they are + * rendered above views. + */ +static void +move_popup_to_top_layer(struct lab_layer_surface *toplevel, + struct lab_layer_popup *popup) +{ + struct server *server = toplevel->server; + struct wlr_output *wlr_output = + toplevel->scene_layer_surface->layer_surface->output; + struct output *output = output_from_wlr_output(server, wlr_output); + struct wlr_box box = { 0 }; + wlr_output_layout_get_box(server->output_layout, wlr_output, &box); + int lx = toplevel->scene_layer_surface->node->state.x + box.x; + int ly = toplevel->scene_layer_surface->node->state.y + box.y; + + struct wlr_scene_node *node = popup->scene_node; + wlr_scene_node_reparent(node, &output->layer_popup_tree->node); + wlr_scene_node_set_position(&output->layer_popup_tree->node, lx, ly); +} + +/* This popup's parent is a shell-layer surface */ static void new_popup_notify(struct wl_listener *listener, void *data) { - struct lab_layer_surface *lab_layer_surface = - wl_container_of(listener, lab_layer_surface, new_popup); + struct lab_layer_surface *toplevel = + wl_container_of(listener, toplevel, new_popup); struct wlr_xdg_popup *wlr_popup = data; - create_popup(wlr_popup, LAYER_PARENT_LAYER, lab_layer_surface); + struct output *output = + toplevel->scene_layer_surface->layer_surface->output->data; + + struct wlr_box output_box = { 0 }; + wlr_output_layout_get_box(output->server->output_layout, + output->wlr_output, &output_box); + + int lx, ly; + wlr_scene_node_coords(toplevel->scene_layer_surface->node, &lx, &ly); + + /* + * Output geometry expressed in the coordinate system of the toplevel + * parent of popup. We store this struct the lab_layer_popup struct + * to make it easier to unconstrain children when we move popups from + * the bottom to the top layer. + */ + struct wlr_box output_toplevel_sx_box = { + .x = output_box.x - lx, + .y = output_box.y - ly, + .width = output_box.width, + .height = output_box.height, + }; + struct lab_layer_popup *popup = create_popup(wlr_popup, + toplevel->scene_layer_surface->node, + &output_toplevel_sx_box); + popup->output_toplevel_sx_box = output_toplevel_sx_box; + + if (toplevel->scene_layer_surface->layer_surface->current.layer + == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { + move_popup_to_top_layer(toplevel, popup); + } } static void @@ -567,15 +317,19 @@ new_layer_surface_notify(struct wl_listener *listener, void *data) surface->new_popup.notify = new_popup_notify; wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); - surface->new_subsurface.notify = new_subsurface_notify; - wl_signal_add(&layer_surface->surface->events.new_subsurface, - &surface->new_subsurface); - - surface->layer_surface = layer_surface; - layer_surface->data = surface; - surface->server = server; - struct output *output = layer_surface->output->data; + + struct wlr_scene_tree *selected_layer = + output->layer_tree[layer_surface->current.layer]; + + surface->scene_layer_surface = wlr_scene_layer_surface_v1_create( + &selected_layer->node, layer_surface); + node_descriptor_create(surface->scene_layer_surface->node, + LAB_NODE_DESC_LAYER_SURFACE, surface); + + surface->server = server; + surface->scene_layer_surface->layer_surface = layer_surface; + surface->output_destroy.notify = output_destroy_notify; wl_signal_add(&layer_surface->output->events.destroy, &surface->output_destroy); @@ -593,7 +347,7 @@ new_layer_surface_notify(struct wl_listener *listener, void *data) */ struct wlr_layer_surface_v1_state old_state = layer_surface->current; layer_surface->current = layer_surface->pending; - arrange_layers(output); + layers_arrange(output); layer_surface->current = old_state; } diff --git a/src/main.c b/src/main.c index 4e419e5e..e3662f26 100644 --- a/src/main.c +++ b/src/main.c @@ -68,7 +68,7 @@ main(int argc, char *argv[]) server_start(&server); struct theme theme = { 0 }; - theme_init(&theme, server.renderer, rc.theme_name); + theme_init(&theme, rc.theme_name); server.theme = &theme; menu_init_rootmenu(&server); diff --git a/src/menu/menu.c b/src/menu/menu.c index 48b18336..2f34d4e3 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -19,6 +19,8 @@ #include "menu/menu.h" #include "theme.h" #include "action.h" +#include "buffer.h" +#include "node.h" #define MENUWIDTH (110) #define MENU_ITEM_PADDING_Y (4) @@ -51,12 +53,19 @@ menu_create(struct server *server, const char *id, const char *label) menu->label = strdup(label); menu->parent = current_menu; menu->server = server; + menu->size.width = MENUWIDTH; + /* menu->size.height will be kept up to date by adding items */ + menu->scene_tree = wlr_scene_tree_create(&server->menu_tree->node); + wlr_scene_node_set_enabled(&menu->scene_tree->node, false); return menu; } -static struct menu * -get_menu_by_id(const char *id) +struct menu * +menu_get_by_id(const char *id) { + if (!id) { + return NULL; + } struct menu *menu; for (int i = 0; i < nr_menus; ++i) { menu = menus + i; @@ -74,6 +83,7 @@ item_create(struct menu *menu, const char *text) if (!menuitem) { return NULL; } + menuitem->parent = menu; struct server *server = menu->server; struct theme *theme = server->theme; struct font font = { @@ -81,19 +91,57 @@ item_create(struct menu *menu, const char *text) .size = rc.font_size_menuitem, }; - menuitem->box.width = MENUWIDTH; - menuitem->box.height = font_height(&font) + 2 * MENU_ITEM_PADDING_Y; + if (!menu->item_height) { + menu->item_height = font_height(&font) + 2 * MENU_ITEM_PADDING_Y; + } + int x, y; int item_max_width = MENUWIDTH - 2 * MENU_ITEM_PADDING_X; - font_texture_create(server, &menuitem->texture.active, item_max_width, - text, &font, theme->menu_items_active_text_color); - font_texture_create(server, &menuitem->texture.inactive, item_max_width, - text, &font, theme->menu_items_text_color); + struct wlr_scene_node *parent = &menu->scene_tree->node; - /* center align vertically */ - menuitem->texture.offset_y = - (menuitem->box.height - menuitem->texture.active->height) / 2; - menuitem->texture.offset_x = MENU_ITEM_PADDING_X; + /* Font buffer */ + font_buffer_create(&menuitem->normal.buffer, item_max_width, + text, &font, theme->menu_items_text_color); + font_buffer_create(&menuitem->selected.buffer, item_max_width, + text, &font, theme->menu_items_active_text_color); + + /* Item background nodes */ + menuitem->normal.background = &wlr_scene_rect_create(parent, + MENUWIDTH, menu->item_height, + theme->menu_items_bg_color)->node; + menuitem->selected.background = &wlr_scene_rect_create(parent, + MENUWIDTH, menu->item_height, + theme->menu_items_active_bg_color)->node; + + /* Font nodes */ + menuitem->normal.text = &wlr_scene_buffer_create( + menuitem->normal.background, &menuitem->normal.buffer->base)->node; + menuitem->selected.text = &wlr_scene_buffer_create( + menuitem->selected.background, &menuitem->selected.buffer->base)->node; + + /* Node descriptors for top scene nodes of menuitem */ + node_descriptor_create(menuitem->normal.background, + LAB_NODE_DESC_MENUITEM, menuitem); + node_descriptor_create(menuitem->selected.background, + LAB_NODE_DESC_MENUITEM, menuitem); + + /* Center font nodes */ + y = (menu->item_height - menuitem->normal.buffer->base.height) / 2; + x = MENU_ITEM_PADDING_X; + wlr_scene_node_set_position(menuitem->normal.text, x, y); + wlr_scene_node_set_position(menuitem->selected.text, x, y); + + /* Position the item in relation to its menu */ + int item_count = wl_list_length(&menu->menuitems); + y = item_count * menu->item_height; + wlr_scene_node_set_position(menuitem->normal.background, 0, y); + wlr_scene_node_set_position(menuitem->selected.background, 0, y); + + /* Hide selected state */ + wlr_scene_node_set_enabled(menuitem->selected.background, false); + + /* Update menu extents */ + menu->size.height = (item_count + 1) * menu->item_height; wl_list_insert(&menu->menuitems, &menuitem->link); wl_list_init(&menuitem->actions); @@ -204,7 +252,7 @@ handle_menu_element(xmlNode *n, struct server *server) current_menu = current_menu->parent; --menu_level; } else if (id) { - struct menu *menu = get_menu_by_id(id); + struct menu *menu = menu_get_by_id(id); if (menu) { current_item = item_create(current_menu, menu->label); current_item->submenu = menu; @@ -280,78 +328,127 @@ err: free(b.buf); } +static int +menu_get_full_width(struct menu *menu) +{ + int width = menu->size.width - menu->server->theme->menu_overlap_x; + int child_width; + int max_child_width = 0; + struct menuitem *item; + wl_list_for_each_reverse(item, &menu->menuitems, link) { + if (!item->submenu) { + continue; + } + child_width = menu_get_full_width(item->submenu); + if (child_width > max_child_width) { + max_child_width = child_width; + } + } + return width + max_child_width; +} + static void -menu_configure(struct menu *menu, int x, int y) +menu_configure(struct menu *menu, int lx, int ly, enum menu_align align) { struct theme *theme = menu->server->theme; - menu->box.x = x; - menu->box.y = y; + /* Get output local coordinates + output usable area */ + double ox = lx; + double oy = ly; + struct wlr_output *wlr_output = wlr_output_layout_output_at( + menu->server->output_layout, lx, ly); + wlr_output_layout_output_coords(menu->server->output_layout, + wlr_output, &ox, &oy); + struct wlr_box usable = output_usable_area_from_cursor_coords(menu->server); - int offset = 0; - struct menuitem *menuitem; - wl_list_for_each_reverse (menuitem, &menu->menuitems, link) { - menuitem->box.x = menu->box.x; - menuitem->box.y = menu->box.y + offset; - offset += menuitem->box.height; - if (menuitem->submenu) { - menu_configure(menuitem->submenu, menuitem->box.x - + MENUWIDTH - theme->menu_overlap_x, - menuitem->box.y + theme->menu_overlap_y); + if (align == LAB_MENU_OPEN_AUTO) { + int full_width = menu_get_full_width(menu); + if (ox + full_width > usable.width) { + align = LAB_MENU_OPEN_LEFT; + } else { + align = LAB_MENU_OPEN_RIGHT; } } - menu->box.width = MENUWIDTH; - menu->box.height = offset; + if (oy + menu->size.height > usable.height) { + align &= ~LAB_MENU_OPEN_BOTTOM; + align |= LAB_MENU_OPEN_TOP; + } else { + align &= ~LAB_MENU_OPEN_TOP; + align |= LAB_MENU_OPEN_BOTTOM; + } + + if (align & LAB_MENU_OPEN_LEFT) { + lx -= MENUWIDTH - theme->menu_overlap_x; + } + if (align & LAB_MENU_OPEN_TOP) { + ly -= menu->size.height; + if (menu->parent) { + /* For submenus adjust y to bottom left corner */ + ly += menu->item_height; + } + } + wlr_scene_node_set_position(&menu->scene_tree->node, lx, ly); + + int rel_y; + int new_lx, new_ly; + struct menuitem *item; + wl_list_for_each_reverse(item, &menu->menuitems, link) { + if (!item->submenu) { + continue; + } + if (align & LAB_MENU_OPEN_RIGHT) { + new_lx = lx + MENUWIDTH - theme->menu_overlap_x; + } else { + new_lx = lx; + } + rel_y = item->normal.background->state.y; + new_ly = ly + rel_y - theme->menu_overlap_y; + menu_configure(item->submenu, new_lx, new_ly, align); + } } void menu_init_rootmenu(struct server *server) { parse_xml("menu.xml", server); - server->rootmenu = get_menu_by_id("root-menu"); + struct menu *menu = menu_get_by_id("root-menu"); /* Default menu if no menu.xml found */ - if (!server->rootmenu) { + if (!menu) { current_menu = NULL; - server->rootmenu = menu_create(server, "root-menu", ""); + menu = menu_create(server, "root-menu", ""); } - if (wl_list_empty(&server->rootmenu->menuitems)) { - current_item = item_create(server->rootmenu, "Reconfigure"); + if (wl_list_empty(&menu->menuitems)) { + current_item = item_create(menu, "Reconfigure"); fill_item("name.action", "Reconfigure"); - current_item = item_create(server->rootmenu, "Exit"); + current_item = item_create(menu, "Exit"); fill_item("name.action", "Exit"); } - - server->rootmenu->visible = true; - menu_configure(server->rootmenu, 100, 100); } void menu_init_windowmenu(struct server *server) { - server->windowmenu = get_menu_by_id("client-menu"); + struct menu *menu = menu_get_by_id("client-menu"); /* Default menu if no menu.xml found */ - if (!server->windowmenu) { + if (!menu) { current_menu = NULL; - server->windowmenu = menu_create(server, "client-menu", ""); + menu = menu_create(server, "client-menu", ""); } - if (wl_list_empty(&server->windowmenu->menuitems)) { - current_item = item_create(server->windowmenu, "Minimize"); + if (wl_list_empty(&menu->menuitems)) { + current_item = item_create(menu, "Minimize"); fill_item("name.action", "Iconify"); - current_item = item_create(server->windowmenu, "Maximize"); + current_item = item_create(menu, "Maximize"); fill_item("name.action", "ToggleMaximize"); - current_item = item_create(server->windowmenu, "Fullscreen"); + current_item = item_create(menu, "Fullscreen"); fill_item("name.action", "ToggleFullscreen"); - current_item = item_create(server->windowmenu, "Decorations"); + current_item = item_create(menu, "Decorations"); fill_item("name.action", "ToggleDecorations"); - current_item = item_create(server->windowmenu, "Close"); + current_item = item_create(menu, "Close"); fill_item("name.action", "Close"); } - - server->windowmenu->visible = true; - menu_configure(server->windowmenu, 100, 100); } void @@ -364,104 +461,130 @@ menu_finish(void) wl_list_for_each_safe(item, next, &menu->menuitems, link) { wl_list_remove(&item->link); action_list_free(&item->actions); + wlr_scene_node_destroy(item->normal.text); + wlr_scene_node_destroy(item->selected.text); + wlr_scene_node_destroy(item->normal.background); + wlr_scene_node_destroy(item->selected.background); + wlr_buffer_drop(&item->normal.buffer->base); + wlr_buffer_drop(&item->selected.buffer->base); free(item); } + wlr_scene_node_destroy(&menu->scene_tree->node); } zfree(menus); alloc_menus = 0; nr_menus = 0; } +/* 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( + menu->selection.item->normal.background, true); + wlr_scene_node_set_enabled( + menu->selection.item->selected.background, false); + } + /* Set new selection */ + if (item) { + wlr_scene_node_set_enabled(item->normal.background, false); + wlr_scene_node_set_enabled(item->selected.background, true); + } + menu->selection.item = item; +} + static void close_all_submenus(struct menu *menu) { struct menuitem *item; wl_list_for_each (item, &menu->menuitems, link) { if (item->submenu) { - item->submenu->visible = false; + wlr_scene_node_set_enabled(&item->submenu->scene_tree->node, false); close_all_submenus(item->submenu); } } + menu->selection.menu = NULL; } void -menu_move(struct menu *menu, int x, int y) +menu_open(struct menu *menu, int x, int y) { assert(menu); + if (menu->server->menu_current) { + menu_close(menu->server->menu_current); + } close_all_submenus(menu); - menu_configure(menu, x, y); + menu_set_selection(menu, NULL); + menu_configure(menu, x, y, LAB_MENU_OPEN_AUTO); + wlr_scene_node_set_enabled(&menu->scene_tree->node, true); + menu->server->menu_current = menu; + menu->server->input_mode = LAB_INPUT_STATE_MENU; } -/* TODO: consider renaming function to menu_process_cursor_motion */ void -menu_set_selected(struct menu *menu, int x, int y) +menu_process_cursor_motion(struct wlr_scene_node *node) { - if (!menu->visible) { + assert(node && node->data); + struct menuitem *item = node_menuitem_from_node(node); + + if (node == item->selected.background) { + /* We are on an already selected item */ return; } - struct menuitem *item; - wl_list_for_each (item, &menu->menuitems, link) { - item->selected = wlr_box_contains_point(&item->box, x, y); - - if (!item->selected) { - if (item->submenu && item->submenu->visible) { - /* - * Handle the case where a submenu is already - * open. - */ - item->selected = true; - menu_set_selected(item->submenu, x, y); - } - continue; - } - - /* We're now on an item that has mouse-focus */ - if (item->submenu) { - if (item->submenu->visible) { - /* do nothing - submenu already open */ - } else { - /* open submenu */ - close_all_submenus(menu); - item->submenu->visible = true; - menu_set_selected(item->submenu, x, y); - } - } else { - close_all_submenus(menu); - } + /* We are on an item that has new mouse-focus */ + menu_set_selection(item->parent, item); + if (item->parent->selection.menu) { + /* Close old submenu tree */ + menu_close(item->parent->selection.menu); } + + if (item->submenu) { + /* And open the new one */ + wlr_scene_node_set_enabled( + &item->submenu->scene_tree->node, true); + } + item->parent->selection.menu = item->submenu; } -static void -menu_clear_selection(struct menu *menu) + +bool +menu_call_actions(struct wlr_scene_node *node) { - struct menuitem *item; - wl_list_for_each (item, &menu->menuitems, link) { - item->selected = false; - if (item->submenu) { - menu_clear_selection(item->submenu); - } + assert(node && node->data); + struct menuitem *item = node_menuitem_from_node(node); + + if (item->submenu) { + /* We received a click on an item that just opens a submenu */ + return false; + } + + actions_run(NULL, item->parent->server, &item->actions, 0); + menu_close(item->parent->server->menu_current); + item->parent->server->menu_current = NULL; + return true; +} + +void +menu_close(struct menu *menu) +{ + if (!menu) { + wlr_log(WLR_ERROR, "Trying to close non exiting menu"); + return; + } + /* TODO: Maybe reset input state here instead of in cursor.c ? */ + wlr_scene_node_set_enabled(&menu->scene_tree->node, false); + menu_set_selection(menu, NULL); + if (menu->selection.menu) { + menu_close(menu->selection.menu); + menu->selection.menu = NULL; } } void -menu_action_selected(struct server *server, struct menu *menu) -{ - struct menuitem *menuitem; - wl_list_for_each (menuitem, &menu->menuitems, link) { - if (menuitem->selected && !menuitem->submenu) { - action(NULL, server, &menuitem->actions, 0); - break; - } - if (menuitem->submenu) { - menu_action_selected(server, menuitem->submenu); - } - } - menu_clear_selection(menu); -} - -void -menu_reconfigure(struct server *server, struct menu *menu) +menu_reconfigure(struct server *server) { menu_finish(); menu_init_rootmenu(server); diff --git a/src/meson.build b/src/meson.build index cb761362..fd918939 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,7 +1,8 @@ labwc_sources = files( 'action.c', + 'buffer.c', 'cursor.c', - 'damage.c', + 'debug.c', 'desktop.c', 'foreign.c', 'interactive.c', @@ -9,16 +10,14 @@ labwc_sources = files( 'key-state.c', 'layers.c', 'main.c', + 'node.c', 'osd.c', 'output.c', 'resistance.c', 'seat.c', 'server.c', - 'ssd.c', - 'subsurface.c', 'theme.c', 'view.c', - 'view-child.c', 'view-impl.c', 'xdg.c', 'xdg-deco.c', @@ -37,3 +36,4 @@ subdir('common') subdir('config') subdir('xbm') subdir('menu') +subdir('ssd') diff --git a/src/node.c b/src/node.c new file mode 100644 index 00000000..f667d82a --- /dev/null +++ b/src/node.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include "node.h" + +static void +descriptor_destroy(struct node_descriptor *node_descriptor) +{ + if (!node_descriptor) { + return; + } + wl_list_remove(&node_descriptor->destroy.link); + free(node_descriptor); +} + +static void +destroy_notify(struct wl_listener *listener, void *data) +{ + struct node_descriptor *node_descriptor = + wl_container_of(listener, node_descriptor, destroy); + descriptor_destroy(node_descriptor); +} + +void +node_descriptor_create(struct wlr_scene_node *scene_node, + enum node_descriptor_type type, void *data) +{ + struct node_descriptor *node_descriptor = + calloc(1, sizeof(struct node_descriptor)); + if (!node_descriptor) { + return; + } + node_descriptor->type = type; + node_descriptor->data = data; + node_descriptor->destroy.notify = destroy_notify; + wl_signal_add(&scene_node->events.destroy, &node_descriptor->destroy); + scene_node->data = node_descriptor; +} + +struct view * +node_view_from_node(struct wlr_scene_node *wlr_scene_node) +{ + assert(wlr_scene_node->data); + struct node_descriptor *node_descriptor = wlr_scene_node->data; + assert(node_descriptor->type == LAB_NODE_DESC_VIEW + || node_descriptor->type == LAB_NODE_DESC_XDG_POPUP); + return (struct view *)node_descriptor->data; +} + +struct lab_layer_surface * +node_layer_surface_from_node(struct wlr_scene_node *wlr_scene_node) +{ + assert(wlr_scene_node->data); + struct node_descriptor *node_descriptor = wlr_scene_node->data; + assert(node_descriptor->type == LAB_NODE_DESC_LAYER_SURFACE); + return (struct lab_layer_surface *)node_descriptor->data; +} + +struct lab_layer_popup * +node_layer_popup_from_node(struct wlr_scene_node *wlr_scene_node) +{ + assert(wlr_scene_node->data); + struct node_descriptor *node_descriptor = wlr_scene_node->data; + assert(node_descriptor->type == LAB_NODE_DESC_LAYER_POPUP); + return (struct lab_layer_popup *)node_descriptor->data; +} + +struct menuitem * +node_menuitem_from_node(struct wlr_scene_node *wlr_scene_node) +{ + assert(wlr_scene_node->data); + struct node_descriptor *node_descriptor = wlr_scene_node->data; + assert(node_descriptor->type == LAB_NODE_DESC_MENUITEM); + return (struct menuitem *)node_descriptor->data; +} diff --git a/src/osd.c b/src/osd.c index f2a54967..3c66d477 100644 --- a/src/osd.c +++ b/src/osd.c @@ -4,6 +4,7 @@ #include #include #include +#include "buffer.h" #include "common/buf.h" #include "common/font.h" #include "config/rcxml.h" @@ -73,22 +74,42 @@ get_osd_height(struct wl_list *views) return height; } +static void +destroy_osd_nodes(struct server *server) +{ + struct wlr_scene_node *child, *next; + struct wl_list *children = &server->osd_tree->node.state.children; + wl_list_for_each_safe(child, next, children, state.link) { + wlr_scene_node_destroy(child); + } +} + +void +osd_finish(struct server *server) +{ + destroy_osd_nodes(server); + wlr_scene_node_set_enabled(&server->osd_tree->node, false); +} + void osd_update(struct server *server) { - struct wlr_renderer *renderer = server->renderer; struct theme *theme = server->theme; + destroy_osd_nodes(server); struct output *output; wl_list_for_each(output, &server->outputs, link) { float scale = output->wlr_output->scale; int w = (OSD_ITEM_WIDTH + (2 * OSD_BORDER_WIDTH)) * scale; int h = get_osd_height(&server->views) * scale; - cairo_surface_t *surf = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); - cairo_surface_set_device_scale(surf, scale, scale); - cairo_t *cairo = cairo_create(surf); + if (output->osd_buffer) { + wlr_buffer_drop(&output->osd_buffer->base); + } + output->osd_buffer = buffer_create_cairo(w, h, scale, true); + + cairo_t *cairo = output->osd_buffer->cairo; + cairo_surface_t *surf = cairo_get_target(cairo); /* background */ set_source(cairo, theme->osd_bg_color); @@ -176,21 +197,14 @@ osd_update(struct server *server) pango_cairo_show_layout(cairo, layout); y += OSD_ITEM_HEIGHT; } - g_object_unref(layout); - - /* convert to wlr_texture */ cairo_surface_flush(surf); - unsigned char *data = cairo_image_surface_get_data(surf); - struct wlr_texture *texture = wlr_texture_from_pixels(renderer, - DRM_FORMAT_ARGB8888, cairo_image_surface_get_stride(surf), - w, h, data); - cairo_destroy(cairo); - cairo_surface_destroy(surf); - if (output->osd) { - wlr_texture_destroy(output->osd); - } - output->osd = texture; + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create( + &server->osd_tree->node, &output->osd_buffer->base); + + /* TODO: set position properly */ + wlr_scene_node_set_position(&scene_buffer->node, 10, 10); + wlr_scene_node_set_enabled(&server->osd_tree->node, true); } } diff --git a/src/output.c b/src/output.c index 9c40fcd5..d24f422b 100644 --- a/src/output.c +++ b/src/output.c @@ -9,949 +9,33 @@ #define _POSIX_C_SOURCE 200809L #include "config.h" #include +#include #include #include +#include #include #include +#include "buffer.h" #include "labwc.h" #include "layers.h" #include "menu/menu.h" +#include "node.h" #include "ssd.h" #include "theme.h" -#define DEBUG (0) - -typedef void (*surface_iterator_func_t)(struct output *output, - struct wlr_surface *surface, struct wlr_box *box, - void *user_data); - -struct surface_iterator_data { - surface_iterator_func_t user_iterator; - void *user_data; - struct output *output; - struct view *view; - double ox, oy; - int width, height; -}; - -static bool -intersects_with_output(struct output *output, - struct wlr_output_layout *output_layout, - struct wlr_box *surface_box) -{ - /* The resolution can change if outputs are rotated */ - struct wlr_box output_box = {0}; - wlr_output_effective_resolution(output->wlr_output, &output_box.width, - &output_box.height); - struct wlr_box intersection; - return wlr_box_intersection(&intersection, &output_box, surface_box); -} - static void -output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy, - void *user_data) -{ - struct surface_iterator_data *data = user_data; - struct output *output = data->output; - if (!wlr_surface_has_buffer(surface)) { - return; - } - struct wlr_box surface_box = { - .x = data->ox + sx + surface->sx, - .y = data->oy + sy + surface->sy, - .width = surface->current.width, - .height = surface->current.height, - }; - - if (!intersects_with_output(output, output->server->output_layout, - &surface_box)) { - return; - } - data->user_iterator(data->output, surface, &surface_box, - data->user_data); -} - -void -output_surface_for_each_surface(struct output *output, - struct wlr_surface *surface, double ox, double oy, - surface_iterator_func_t iterator, void *user_data) -{ - struct surface_iterator_data data = { - .user_iterator = iterator, - .user_data = user_data, - .output = output, - .ox = ox, - .oy = oy, - }; - assert(surface); - wlr_surface_for_each_surface(surface, - output_for_each_surface_iterator, &data); -} - -struct render_data { - pixman_region32_t *damage; -}; - -int -scale_length(int length, int offset, float scale) -{ - return round((offset + length) * scale) - round(offset * scale); -} - -void -scale_box(struct wlr_box *box, float scale) -{ - box->width = scale_length(box->width, box->x, scale); - box->height = scale_length(box->height, box->y, scale); - box->x = round(box->x * scale); - box->y = round(box->y * scale); -} - -static void -scissor_output(struct wlr_output *output, pixman_box32_t *rect) -{ - struct wlr_renderer *renderer = output->renderer; - - struct wlr_box box = { - .x = rect->x1, - .y = rect->y1, - .width = rect->x2 - rect->x1, - .height = rect->y2 - rect->y1, - }; - - int output_width, output_height; - wlr_output_transformed_resolution(output, &output_width, &output_height); - enum wl_output_transform transform = - wlr_output_transform_invert(output->transform); - wlr_box_transform(&box, &box, transform, output_width, output_height); - - wlr_renderer_scissor(renderer, &box); -} - -static void -render_texture(struct wlr_output *wlr_output, - pixman_region32_t *output_damage, struct wlr_texture *texture, - const struct wlr_fbox *src_box, const struct wlr_box *dst_box, - const float matrix[static 9]) -{ - struct wlr_renderer *renderer = wlr_output->renderer; - - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_union_rect(&damage, &damage, dst_box->x, dst_box->y, - dst_box->width, dst_box->height); - pixman_region32_intersect(&damage, &damage, output_damage); - bool damaged = pixman_region32_not_empty(&damage); - if (!damaged) { - goto damage_finish; - } - - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); - for (int i = 0; i < nrects; i++) { - scissor_output(wlr_output, &rects[i]); - const float alpha = 1.0f; - if (src_box != NULL) { - wlr_render_subtexture_with_matrix(renderer, texture, src_box, matrix, alpha); - } else { - wlr_render_texture_with_matrix(renderer, texture, matrix, alpha); - } - } - -damage_finish: - pixman_region32_fini(&damage); -} - -static void -render_surface_iterator(struct output *output, struct wlr_surface *surface, - struct wlr_box *_box, void *user_data) -{ - struct render_data *data = user_data; - struct wlr_output *wlr_output = output->wlr_output; - pixman_region32_t *output_damage = data->damage; - - struct wlr_texture *texture = wlr_surface_get_texture(surface); - if (!texture) { - wlr_log(WLR_DEBUG, "Cannot obtain surface texture"); - return; - } - - struct wlr_fbox src_box; - wlr_surface_get_buffer_source_box(surface, &src_box); - - struct wlr_box proj_box = *_box; - scale_box(&proj_box, wlr_output->scale); - - float matrix[9]; - enum wl_output_transform transform = - wlr_output_transform_invert(surface->current.transform); - wlr_matrix_project_box(matrix, &proj_box, transform, 0.0, - wlr_output->transform_matrix); - - struct wlr_box dst_box = *_box; - scale_box(&dst_box, wlr_output->scale); - - render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, matrix); -} - -void -output_drag_icon_for_each_surface(struct output *output, struct seat *seat, - surface_iterator_func_t iterator, void *user_data) -{ - if (!seat->drag_icon || !seat->drag_icon->mapped) { - return; - } - double ox = seat->cursor->x, oy = seat->cursor->y; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - output_surface_for_each_surface(output, seat->drag_icon->surface, - ox, oy, iterator, user_data); -} - -static void -render_drag_icon(struct output *output, pixman_region32_t *damage) -{ - struct render_data data = { - .damage = damage, - }; - output_drag_icon_for_each_surface(output, &output->server->seat, render_surface_iterator, &data); -} - -#if HAVE_XWAYLAND -void -output_unmanaged_for_each_surface(struct output *output, - struct wl_list *unmanaged, surface_iterator_func_t iterator, - void *user_data) -{ - struct xwayland_unmanaged *unmanaged_surface; - wl_list_for_each(unmanaged_surface, unmanaged, link) { - struct wlr_xwayland_surface *xsurface = - unmanaged_surface->xwayland_surface; - double ox = unmanaged_surface->lx, oy = unmanaged_surface->ly; - wlr_output_layout_output_coords( - output->server->output_layout, output->wlr_output, &ox, &oy); - output_surface_for_each_surface(output, xsurface->surface, ox, oy, - iterator, user_data); - } -} - -static void -render_unmanaged(struct output *output, pixman_region32_t *damage, - struct wl_list *unmanaged) -{ - struct render_data data = { - .damage = damage, - }; - output_unmanaged_for_each_surface(output, unmanaged, - render_surface_iterator, &data); -} -#endif - -static void -output_view_for_each_surface(struct output *output, struct view *view, - surface_iterator_func_t iterator, void *user_data) -{ - struct surface_iterator_data data = { - .user_iterator = iterator, - .user_data = user_data, - .output = output, - .ox = view->x, - .oy = view->y, - }; - - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &data.ox, &data.oy); - view_for_each_surface(view, output_for_each_surface_iterator, &data); -} - -void -output_view_for_each_popup_surface(struct output *output, struct view *view, - surface_iterator_func_t iterator, void *user_data) -{ - struct surface_iterator_data data = { - .user_iterator = iterator, - .user_data = user_data, - .output = output, - .ox = view->x, - .oy = view->y, - }; - - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &data.ox, &data.oy); - view_for_each_popup_surface(view, output_for_each_surface_iterator, &data); -} - -/* for sending frame done */ -void -output_layer_for_each_surface(struct output *output, - struct wl_list *layer_surfaces, surface_iterator_func_t iterator, - void *user_data) -{ - struct lab_layer_surface *layer_surface; - wl_list_for_each(layer_surface, layer_surfaces, link) { - struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = - layer_surface->layer_surface; - struct wlr_surface *surface = wlr_layer_surface_v1->surface; - struct surface_iterator_data data = { - .user_iterator = iterator, - .user_data = user_data, - .output = output, - .view = NULL, - .ox = layer_surface->geo.x, - .oy = layer_surface->geo.y, - .width = surface->current.width, - .height = surface->current.height, - }; - wlr_layer_surface_v1_for_each_surface(wlr_layer_surface_v1, - output_for_each_surface_iterator, &data); - } -} - -static void -output_for_each_surface(struct output *output, surface_iterator_func_t iterator, - void *user_data) -{ - output_layer_for_each_surface(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], - iterator, user_data); - output_layer_for_each_surface(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], - iterator, user_data); - - struct view *view; - wl_list_for_each_reverse(view, &output->server->views, link) { - if (!view->mapped) { - continue; - } - output_view_for_each_surface(output, view, iterator, user_data); - } - -#if HAVE_XWAYLAND - output_unmanaged_for_each_surface(output, - &output->server->unmanaged_surfaces, iterator, user_data); -#endif - - output_layer_for_each_surface(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], - iterator, user_data); - output_layer_for_each_surface(output, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], - iterator, user_data); - output_drag_icon_for_each_surface(output, &output->server->seat, iterator, user_data); -} - -struct send_frame_done_data { - struct timespec when; -}; - -static void -send_frame_done_iterator(struct output *output, struct wlr_surface *surface, - struct wlr_box *box, void *user_data) -{ - struct send_frame_done_data *data = user_data; - wlr_surface_send_frame_done(surface, &data->when); -} - -static void -send_frame_done(struct output *output, struct send_frame_done_data *data) -{ - output_for_each_surface(output, send_frame_done_iterator, data); -} - -void -render_rect(struct output *output, pixman_region32_t *output_damage, - const struct wlr_box *_box, float color[static 4]) -{ - struct wlr_output *wlr_output = output->wlr_output; - struct wlr_renderer *renderer = wlr_output->renderer; - - struct wlr_box box; - memcpy(&box, _box, sizeof(struct wlr_box)); - - double ox = 0, oy = 0; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - box.x += ox; - box.y += oy; - scale_box(&box, wlr_output->scale); - - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_union_rect(&damage, &damage, box.x, box.y, - box.width, box.height); - pixman_region32_intersect(&damage, &damage, output_damage); - bool damaged = pixman_region32_not_empty(&damage); - if (!damaged) { - goto damage_finish; - } - - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(wlr_output, &rects[i]); - wlr_render_rect(renderer, &box, color, - wlr_output->transform_matrix); - } - -damage_finish: - pixman_region32_fini(&damage); -} - -void -render_rect_unfilled(struct output *output, pixman_region32_t *output_damage, - const struct wlr_box *_box, float color[static 4]) -{ - struct wlr_box box; - memcpy(&box, _box, sizeof(struct wlr_box)); - box.height = 1; - render_rect(output, output_damage, &box, color); - box.y += _box->height - 1; - render_rect(output, output_damage, &box, color); - memcpy(&box, _box, sizeof(struct wlr_box)); - box.width = 1; - render_rect(output, output_damage, &box, color); - box.x += _box->width - 1; - render_rect(output, output_damage, &box, color); -} - -static void -shrink(struct wlr_box *box, int size) -{ - box->x += size; - box->y += size; - box->width -= 2 * size; - box->height -= 2 * size; -} - -static void -render_cycle_box(struct output *output, pixman_region32_t *output_damage, - struct view *view) -{ - struct wlr_box box = { - .x = view->x, - .y = view->y, - .width = view->w, - .height = view->h, - }; - box.x -= view->margin.left; - box.y -= view->margin.top; - box.width += view->margin.left + view->margin.right; - box.height += view->margin.top + view->margin.bottom; - box.x += view->padding.left; - box.y += view->padding.top; - - float white[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; - float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; - render_rect_unfilled(output, output_damage, &box, white); - - for (int i = 0; i < 4; i++) { - shrink(&box, 1); - render_rect_unfilled(output, output_damage, &box, black); - } - shrink(&box, 1); - render_rect_unfilled(output, output_damage, &box, white); -} - -static void -render_icon(struct output *output, pixman_region32_t *output_damage, - struct wlr_box *box, struct wlr_texture *texture) -{ - /* centre-align icon if smaller than designated box */ - struct wlr_box button = { - .width = texture->width, - .height = texture->height, - }; - if (box->width > button.width) { - button.x = box->x + (box->width - button.width) / 2; - } else { - button.x = box->x; - button.width = box->width; - } - if (box->height > button.height) { - button.y = box->y + (box->height - button.height) / 2; - } else { - button.y = box->y; - button.height = box->height; - } - - double ox = 0, oy = 0; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - button.x += ox; - button.y += oy; - scale_box(&button, output->wlr_output->scale); - - float matrix[9]; - wlr_matrix_project_box(matrix, &button, WL_OUTPUT_TRANSFORM_NORMAL, 0, - output->wlr_output->transform_matrix); - render_texture(output->wlr_output, output_damage, texture, NULL, &button, - matrix); -} - -void -render_texture_helper(struct output *output, pixman_region32_t *output_damage, - struct wlr_box *_box, struct wlr_texture *texture) -{ - if (!texture) { - return; - } - struct wlr_box box; - memcpy(&box, _box, sizeof(struct wlr_box)); - - double ox = 0, oy = 0; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - box.x += ox; - box.y += oy; - scale_box(&box, output->wlr_output->scale); - - float matrix[9]; - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, - output->wlr_output->transform_matrix); - render_texture(output->wlr_output, output_damage, texture, NULL, &box, - matrix); -} - -static void -render_osd(struct output *output, pixman_region32_t *damage, - struct server *server) -{ - /* show on screen display (osd) on all outputs */ - struct output *o; - struct wlr_output_layout *layout = server->output_layout; - struct wlr_output_layout_output *ol_output; - wl_list_for_each(o, &server->outputs, link) { - ol_output = wlr_output_layout_get(layout, o->wlr_output); - if (!ol_output) { - continue; - } - - if (!output->osd) { - continue; - } - struct wlr_box box = { - .x = ol_output->x + o->wlr_output->width - / 2, - .y = ol_output->y + o->wlr_output->height - / 2, - .width = output->osd->width, - .height = output->osd->height, - }; - box.x -= box.width / 2; - box.y -= box.height / 2; - - double ox = 0, oy = 0; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - box.x += ox; - box.y += oy; - float matrix[9]; - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, - 0, output->wlr_output->transform_matrix); - render_texture(o->wlr_output, damage, output->osd, NULL, &box, - matrix); - } -} - -static void -render_deco(struct view *view, struct output *output, - pixman_region32_t *output_damage) -{ - if (!view->ssd.enabled || view->fullscreen) { - return; - } - - struct wlr_seat *seat = view->server->seat.seat; - bool focused = view->surface == seat->keyboard_state.focused_surface; - - /* render texture or rectangle */ - struct ssd_part *part; - wl_list_for_each_reverse(part, &view->ssd.parts, link) { - if (part->texture.active && *(part->texture.active)) { - struct wlr_texture *texture = focused ? - *(part->texture.active) : - *(part->texture.inactive); - render_texture_helper(output, output_damage, &part->box, - texture); - } else if (part->color.active && part->color.inactive) { - float *color = focused ? - part->color.active : - part->color.inactive; - render_rect(output, output_damage, &part->box, color); - } - } - - /* button background */ - struct wlr_cursor *cur = view->server->seat.cursor; - enum ssd_part_type type = ssd_at(view, cur->x, cur->y); - struct wlr_box box = ssd_visible_box(view, type); - if (ssd_is_button(type) && - wlr_box_contains_point(&box, cur->x, cur->y)) { - float *color = (float[4]) { 0.5, 0.5, 0.5, 0.5 }; - render_rect(output, output_damage, &box, color); - } - - /* buttons */ - struct theme *theme = view->server->theme; - if (view->surface == seat->keyboard_state.focused_surface) { - box = ssd_visible_box(view, LAB_SSD_BUTTON_CLOSE); - render_icon(output, output_damage, &box, - theme->xbm_close_active_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_MAXIMIZE); - render_icon(output, output_damage, &box, - theme->xbm_maximize_active_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_ICONIFY); - render_icon(output, output_damage, &box, - theme->xbm_iconify_active_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_WINDOW_MENU); - render_icon(output, output_damage, &box, - theme->xbm_menu_active_unpressed); - } else { - box = ssd_visible_box(view, LAB_SSD_BUTTON_CLOSE); - render_icon(output, output_damage, &box, - theme->xbm_close_inactive_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_MAXIMIZE); - render_icon(output, output_damage, &box, - theme->xbm_maximize_inactive_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_ICONIFY); - render_icon(output, output_damage, &box, - theme->xbm_iconify_inactive_unpressed); - box = ssd_visible_box(view, LAB_SSD_BUTTON_WINDOW_MENU); - render_icon(output, output_damage, &box, - theme->xbm_menu_inactive_unpressed); - } -} - -static void -render_menu(struct output *output, pixman_region32_t *damage, struct menu *menu) -{ - if (!menu->visible) { - return; - } - struct server *server = output->server; - struct theme *theme = server->theme; - float matrix[9]; - - struct wlr_output_layout *output_layout = server->output_layout; - double ox = 0, oy = 0; - wlr_output_layout_output_coords(output_layout, output->wlr_output, - &ox, &oy); - - /* background */ - render_rect(output, damage, &menu->box, theme->menu_items_bg_color); - - /* items */ - struct menuitem *menuitem; - wl_list_for_each (menuitem, &menu->menuitems, link) { - struct wlr_box box = { - .x = menuitem->box.x + menuitem->texture.offset_x + ox, - .y = menuitem->box.y + menuitem->texture.offset_y + oy, - .width = menuitem->texture.active->width, - .height = menuitem->texture.active->height, - }; - scale_box(&box, output->wlr_output->scale); - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, - 0, output->wlr_output->transform_matrix); - if (menuitem->selected) { - render_rect(output, damage, &menuitem->box, - theme->menu_items_active_bg_color); - render_texture(output->wlr_output, damage, - menuitem->texture.active, NULL, &box, matrix); - } else { - render_texture(output->wlr_output, damage, - menuitem->texture.inactive, NULL, &box, matrix); - } - /* render submenus */ - if (menuitem->submenu) { - render_menu(output, damage, menuitem->submenu); - } - } -} - -static void -output_layer_for_each_popup_surface(struct output *output, - struct wl_list *layer_surfaces, - surface_iterator_func_t iterator, - void *user_data) -{ - struct lab_layer_surface *layer_surface; - wl_list_for_each(layer_surface, layer_surfaces, link) { - struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = - layer_surface->layer_surface; - struct wlr_surface *surface = wlr_layer_surface_v1->surface; - struct surface_iterator_data data = { - .user_iterator = iterator, - .user_data = user_data, - .output = output, - .view = NULL, - .ox = layer_surface->geo.x, - .oy = layer_surface->geo.y, - .width = surface->current.width, - .height = surface->current.height, - }; - wlr_layer_surface_v1_for_each_popup_surface(wlr_layer_surface_v1, - output_for_each_surface_iterator, &data); - } -} - -static void -render_layer_popups(struct output *output, pixman_region32_t *damage, - struct wl_list *layer_surfaces) -{ - struct render_data data = { - .damage = damage, - }; - output_layer_for_each_popup_surface(output, layer_surfaces, - render_surface_iterator, &data); -} - -void -output_layer_for_each_surface_toplevel(struct output *output, - struct wl_list *layer_surfaces, surface_iterator_func_t iterator, - void *user_data) -{ - struct lab_layer_surface *layer_surface; - wl_list_for_each(layer_surface, layer_surfaces, link) { - struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = - layer_surface->layer_surface; - output_surface_for_each_surface(output, - wlr_layer_surface_v1->surface, layer_surface->geo.x, - layer_surface->geo.y, iterator, user_data); - } -} - -static void -render_layer_toplevel(struct output *output, pixman_region32_t *damage, - struct wl_list *layer_surfaces) -{ - struct render_data data = { - .damage = damage, - }; - output_layer_for_each_surface_toplevel(output, layer_surfaces, - render_surface_iterator, &data); -} - -static void -render_view_toplevels(struct view *view, struct output *output, - pixman_region32_t *damage) -{ - struct render_data data = { - .damage = damage, - }; - double ox = view->x; - double oy = view->y; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - output_surface_for_each_surface(output, view->surface, ox, oy, - render_surface_iterator, &data); -} - -static void -render_view_popups(struct view *view, struct output *output, - pixman_region32_t *damage) -{ - struct render_data data = { - .damage = damage, - }; - output_view_for_each_popup_surface(output, view, - render_surface_iterator, &data); -} - -void -output_render(struct output *output, pixman_region32_t *damage) -{ - struct server *server = output->server; - struct wlr_output *wlr_output = output->wlr_output; - - struct wlr_renderer *renderer = wlr_output->renderer; - if (!renderer) { - wlr_log(WLR_DEBUG, "no renderer"); - return; - } - - /* Calls glViewport and some other GL sanity checks */ - wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); - - if (!pixman_region32_not_empty(damage)) { - goto renderer_end; - } - -#if DEBUG - wlr_renderer_clear(renderer, (float[]){0.2f, 0.0f, 0.0f, 1.0f}); -#endif - - float color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); - for (int i = 0; i < nrects; i++) { - scissor_output(wlr_output, &rects[i]); - wlr_renderer_clear(renderer, color); - } - - render_layer_toplevel(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); - render_layer_toplevel(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); - - struct view *view; - wl_list_for_each_reverse (view, &server->views, link) { - if (!view->mapped) { - continue; - } - render_deco(view, output, damage); - render_view_toplevels(view, output, damage); - render_view_popups(view, output, damage); - } - -#if HAVE_XWAYLAND - render_unmanaged(output, damage, &output->server->unmanaged_surfaces); -#endif - - /* 'alt-tab' border */ - if (output->server->cycle_view) { - /* If the 'cycle_preview_contents' option is set in - * rc.xml, render the contents of the cycle_view over - * all other views (except for the OSD) - */ - if (rc.cycle_preview_contents) { - render_deco(output->server->cycle_view, output, damage); - render_view_toplevels(output->server->cycle_view, output, damage); - render_view_popups(output->server->cycle_view, output, damage); - } - - render_cycle_box(output, damage, output->server->cycle_view); - render_osd(output, damage, output->server); - } - - render_drag_icon(output, damage); - - render_layer_toplevel(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); - render_layer_popups(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); - render_layer_popups(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); - render_layer_popups(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); - - render_layer_toplevel(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); - render_layer_popups(output, damage, - &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); - - if (output->server->input_mode == LAB_INPUT_STATE_MENU) { - render_menu(output, damage, server->rootmenu); - render_menu(output, damage, server->windowmenu); - } - -renderer_end: - /* Just in case hardware cursors not supported by GPU */ - wlr_output_render_software_cursors(wlr_output, damage); - wlr_renderer_scissor(renderer, NULL); - wlr_renderer_end(renderer); - - int output_width, output_height; - wlr_output_transformed_resolution(wlr_output, &output_width, - &output_height); - - pixman_region32_t frame_damage; - pixman_region32_init(&frame_damage); - - enum wl_output_transform transform = - wlr_output_transform_invert(wlr_output->transform); - wlr_region_transform(&frame_damage, &output->damage->current, - transform, output_width, output_height); - -#if DEBUG - pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, - output_width, output_height); -#endif - - wlr_output_set_damage(wlr_output, &frame_damage); - pixman_region32_fini(&frame_damage); - - if (!wlr_output_commit(wlr_output)) { - wlr_log(WLR_ERROR, "could not commit output"); - } -} - -static void -damage_surface_iterator(struct output *output, struct wlr_surface *surface, - struct wlr_box *box, void *user_data) -{ - struct wlr_output *wlr_output = output->wlr_output; - bool whole = *(bool *) user_data; - - scale_box(box, output->wlr_output->scale); - - if (whole) { - wlr_output_damage_add_box(output->damage, box); - } else if (pixman_region32_not_empty(&surface->buffer_damage)) { - pixman_region32_t damage; - pixman_region32_init(&damage); - wlr_surface_get_effective_damage(surface, &damage); - - wlr_region_scale(&damage, &damage, wlr_output->scale); - if (ceil(wlr_output->scale) > surface->current.scale) { - wlr_region_expand(&damage, &damage, - ceil(wlr_output->scale) - surface->current.scale); - } - pixman_region32_translate(&damage, box->x, box->y); - wlr_output_damage_add(output->damage, &damage); - pixman_region32_fini(&damage); - } -} - -void -output_damage_surface(struct output *output, struct wlr_surface *surface, - double lx, double ly, bool whole) +output_frame_notify(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); if (!output->wlr_output->enabled) { return; } - double ox = lx, oy = ly; - wlr_output_layout_output_coords(output->server->output_layout, - output->wlr_output, &ox, &oy); - output_surface_for_each_surface(output, surface, ox, oy, - damage_surface_iterator, &whole); -} + wlr_scene_output_commit(output->scene_output); -static void -output_damage_frame_notify(struct wl_listener *listener, void *data) -{ - struct output *output = wl_container_of(listener, output, damage_frame); - - if (!output->wlr_output->enabled) { - return; - } - - bool needs_frame; - pixman_region32_t damage; - pixman_region32_init(&damage); - if (!wlr_output_damage_attach_render(output->damage, - &needs_frame, &damage)) { - return; - } - - if (needs_frame) { - output_render(output, &damage); - } else { - wlr_output_rollback(output->wlr_output); - } - pixman_region32_fini(&damage); - - struct send_frame_done_data frame_data = {0}; - clock_gettime(CLOCK_MONOTONIC, &frame_data.when); - send_frame_done(output, &frame_data); -} - -static void -output_damage_destroy_notify(struct wl_listener *listener, void *data) -{ - struct output *output = wl_container_of(listener, output, damage_destroy); - wl_list_remove(&output->damage_frame.link); - wl_list_remove(&output->damage_destroy.link); + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(output->scene_output, &now); } static void @@ -959,6 +43,7 @@ output_destroy_notify(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, destroy); wl_list_remove(&output->link); + wl_list_remove(&output->frame.link); wl_list_remove(&output->destroy.link); } @@ -1018,27 +103,46 @@ new_output_notify(struct wl_listener *listener, void *data) output->wlr_output = wlr_output; wlr_output->data = output; output->server = server; - output->damage = wlr_output_damage_create(wlr_output); wlr_output_effective_resolution(wlr_output, &output->usable_area.width, &output->usable_area.height); wl_list_insert(&server->outputs, &output->link); output->destroy.notify = output_destroy_notify; wl_signal_add(&wlr_output->events.destroy, &output->destroy); + output->frame.notify = output_frame_notify; + wl_signal_add(&wlr_output->events.frame, &output->frame); - output->damage_frame.notify = output_damage_frame_notify; - wl_signal_add(&output->damage->events.frame, &output->damage_frame); - output->damage_destroy.notify = output_damage_destroy_notify; - wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); - - wl_list_init(&output->layers[0]); - wl_list_init(&output->layers[1]); - wl_list_init(&output->layers[2]); - wl_list_init(&output->layers[3]); /* - * Arrange outputs from left-to-right in the order they appear. - * TODO: support configuration in run-time + * Create layer-trees (background, bottom, top and overlay) and + * a layer-popup-tree. */ + int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]); + for (int i = 0; i < nr_layers; i++) { + wl_list_init(&output->layers[i]); + output->layer_tree[i] = + wlr_scene_tree_create(&server->scene->node); + node_descriptor_create(&output->layer_tree[i]->node, + LAB_NODE_DESC_TREE, NULL); + } + output->layer_popup_tree = wlr_scene_tree_create(&server->scene->node); + node_descriptor_create(&output->layer_popup_tree->node, + LAB_NODE_DESC_TREE, NULL); + + /* + * Set the z-positions to achieve the following order (from top to + * bottom): + * - layer-shell popups + * - overlay layer + * - top layer + * - views + * - bottom layer + * - background layer + */ + wlr_scene_node_lower_to_bottom(&output->layer_tree[1]->node); + wlr_scene_node_lower_to_bottom(&output->layer_tree[0]->node); + wlr_scene_node_raise_to_top(&output->layer_tree[2]->node); + wlr_scene_node_raise_to_top(&output->layer_tree[3]->node); + wlr_scene_node_raise_to_top(&output->layer_popup_tree->node); if (rc.adaptive_sync) { wlr_log(WLR_INFO, "enable adaptive sync on %s", @@ -1046,9 +150,17 @@ new_output_notify(struct wl_listener *listener, void *data) wlr_output_enable_adaptive_sync(wlr_output, true); } - output->osd = NULL; - wlr_output_layout_add_auto(server->output_layout, wlr_output); + + /* TODO: check this makes sense */ + struct wlr_scene_output *scene_output; + wl_list_for_each (scene_output, &output->server->scene->outputs, link) { + if (scene_output->output == wlr_output) { + output->scene_output = scene_output; + break; + } + } + assert(output->scene_output); } void @@ -1066,6 +178,7 @@ output_init(struct server *server) wlr_log(WLR_ERROR, "unable to create output layout"); exit(EXIT_FAILURE); } + wlr_scene_attach_output_layout(server->scene, server->output_layout); /* Enable screen recording with wf-recorder */ wlr_xdg_output_manager_v1_create(server->wl_display, @@ -1096,9 +209,6 @@ output_update_for_layout_change(struct server *server) wlr_cursor_move(server->seat.cursor, NULL, 0, 0); wlr_xcursor_manager_set_cursor_image(server->seat.xcursor_manager, XCURSOR_DEFAULT, server->seat.cursor); - - /* Redraw everything */ - damage_all_outputs(server); } static void @@ -1117,6 +227,7 @@ output_config_apply(struct server *server, bool need_to_remove = !head->state.enabled && o->enabled; if (need_to_remove) { + /* TODO: should we output->scene_output = NULL; ?? */ wlr_output_layout_remove(server->output_layout, o); } @@ -1199,12 +310,12 @@ wlr_output_configuration_v1 *create_output_config(struct server *server) wlr_output_configuration_v1_destroy(config); return NULL; } - struct wlr_box *box = - wlr_output_layout_get_box(server->output_layout, - output->wlr_output); - if (box) { - head->state.x = box->x; - head->state.y = box->y; + struct wlr_box box; + wlr_output_layout_get_box(server->output_layout, + output->wlr_output, &box); + if (!wlr_box_empty(&box)) { + head->state.x = box.x; + head->state.y = box.y; } else { wlr_log(WLR_ERROR, "failed to get output layout box"); } @@ -1230,9 +341,10 @@ handle_output_layout_change(struct wl_listener *listener, void *data) "wlr_output_manager_v1_set_configuration()"); } struct output *output; + wl_list_for_each(output, &server->outputs, link) { if (output) { - arrange_layers(output); + layers_arrange(output); } } output_update_for_layout_change(server); diff --git a/src/seat.c b/src/seat.c index 1f3d5d2a..45e40bf4 100644 --- a/src/seat.c +++ b/src/seat.c @@ -310,7 +310,6 @@ seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer) if (!layer) { seat->focused_layer = NULL; desktop_focus_topmost_mapped_view(seat->server); - damage_all_outputs(seat->server); return; } seat_focus_surface(seat, layer->surface); diff --git a/src/server.c b/src/server.c index 450b71e8..a5fd3550 100644 --- a/src/server.c +++ b/src/server.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ reload_config_and_theme(void) rcxml_finish(); rcxml_read(NULL); theme_finish(g_server->theme); - theme_init(g_server->theme, g_server->renderer, rc.theme_name); + theme_init(g_server->theme, rc.theme_name); struct view *view; wl_list_for_each (view, &g_server->views, link) { @@ -40,12 +41,11 @@ reload_config_and_theme(void) continue; } view->margin = ssd_thickness(view); - ssd_update_geometry(view, true); + ssd_reload(view); } - menu_reconfigure(g_server, g_server->rootmenu); + menu_reconfigure(g_server); seat_reconfigure(g_server); - damage_all_outputs(g_server); } static int @@ -120,7 +120,7 @@ seat_disinhibit_input(struct seat *seat) */ struct output *output; wl_list_for_each(output, &seat->server->outputs, link) { - arrange_layers(output); + layers_arrange(output); } } @@ -216,6 +216,20 @@ server_init(struct server *server) wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); + server->scene = wlr_scene_create(); + if (!server->scene) { + wlr_log(WLR_ERROR, "unable to create scene"); + exit(EXIT_FAILURE); + } + server->view_tree = wlr_scene_tree_create(&server->scene->node); +#if HAVE_XWAYLAND + server->unmanaged_tree = wlr_scene_tree_create(&server->scene->node); +#endif + server->menu_tree = wlr_scene_tree_create(&server->scene->node); + server->osd_tree = wlr_scene_tree_create(&server->scene->node); + + output_init(server); + /* * Create some hands-off wlroots interfaces. The compositor is * necessary for clients to allocate surfaces and the data device @@ -229,6 +243,7 @@ server_init(struct server *server) wlr_log(WLR_ERROR, "unable to create the wlroots compositor"); exit(EXIT_FAILURE); } + wlr_subcompositor_create(server->wl_display); struct wlr_data_device_manager *device_manager = NULL; device_manager = wlr_data_device_manager_create(server->wl_display); @@ -249,7 +264,6 @@ server_init(struct server *server) */ wlr_primary_selection_v1_device_manager_create(server->wl_display); - output_init(server); seat_init(server); /* Init xdg-shell */ @@ -284,6 +298,14 @@ server_init(struct server *server) WLR_SERVER_DECORATION_MANAGER_MODE_SERVER : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); + struct wlr_presentation *presentation = + wlr_presentation_create(server->wl_display, server->backend); + if (!presentation) { + wlr_log(WLR_ERROR, "unable to create presentation interface"); + exit(EXIT_FAILURE); + } + wlr_scene_set_presentation(server->scene, presentation); + wlr_export_dmabuf_manager_v1_create(server->wl_display); wlr_screencopy_manager_v1_create(server->wl_display); wlr_data_control_manager_v1_create(server->wl_display); @@ -394,6 +416,8 @@ server_start(struct server *server) void server_finish(struct server *server) { + +/* TODO: clean up various scene_tree nodes */ #if HAVE_XWAYLAND wlr_xwayland_destroy(server->xwayland); #endif diff --git a/src/ssd.c b/src/ssd.c deleted file mode 100644 index 0897fdaf..00000000 --- a/src/ssd.c +++ /dev/null @@ -1,466 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Helpers for view server side decorations - * - * Copyright (C) Johan Malm 2020-2021 - */ - -#include -#include "config/rcxml.h" -#include "common/font.h" -#include "labwc.h" -#include "theme.h" -#include "ssd.h" - -#define INVISIBLE_MARGIN (16) - -struct border -ssd_thickness(struct view *view) -{ - struct theme *theme = view->server->theme; - struct border border = { - .top = theme->title_height + theme->border_width, - .bottom = theme->border_width, - .left = theme->border_width, - .right = theme->border_width, - }; - return border; -} - -struct wlr_box -ssd_max_extents(struct view *view) -{ - struct border border = ssd_thickness(view); - struct wlr_box box = { - .x = view->x - border.left, - .y = view->y - border.top, - .width = view->w + border.left + border.right, - .height = view->h + border.top + border.bottom, - }; - return box; -} - -#define NR_BUTTONS (4) - -/** - * ssd_box - the 'full' decoration geometry which includes both visible - * and invisible parts. It typically includes an invisible margin outside - * the decoration. - * - * This function is used for determining decoration parts during user- - * interactive operations such as mouse hover or button press - */ -static struct wlr_box -ssd_box(struct view *view, enum ssd_part_type type) -{ - struct theme *theme = view->server->theme; - struct wlr_box box = { 0 }; - int16_t button_height = theme->title_height; - int16_t button_width = theme->title_height; - int16_t corner_square = theme->title_height + theme->border_width; - int16_t title_x_padding = (double)corner_square / 2; - - switch (type) { - case LAB_SSD_BUTTON_CLOSE: - box.x = view->x + view->w - button_width * 1; - box.y = view->y - button_height; - box.width = button_width; - box.height = button_height; - break; - case LAB_SSD_BUTTON_MAXIMIZE: - box.x = view->x + view->w - button_width * 2; - box.y = view->y - button_height; - box.width = button_width; - box.height = button_height; - break; - case LAB_SSD_BUTTON_ICONIFY: - box.x = view->x + view->w - button_width * 3; - box.y = view->y - button_height; - box.width = button_width; - box.height = button_height; - break; - case LAB_SSD_BUTTON_WINDOW_MENU: - box.x = view->x; - box.y = view->y - button_height; - box.width = button_width; - box.height = button_height; - break; - case LAB_SSD_PART_TITLEBAR: - box.x = view->x; - box.y = view->y - theme->title_height; - box.width = view->w; - box.height = theme->title_height; - break; - case LAB_SSD_PART_TITLE: - box.x = view->x + button_width + title_x_padding; - box.y = view->y - theme->title_height; - box.width = view->w - title_x_padding * 2 - NR_BUTTONS * button_width; - box.height = theme->title_height; - break; - case LAB_SSD_PART_CORNER_TOP_LEFT: - box.x = view->x - theme->border_width - INVISIBLE_MARGIN; - box.y = view->y - corner_square - INVISIBLE_MARGIN; - box.width = corner_square + INVISIBLE_MARGIN; - box.height = corner_square + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_CORNER_TOP_RIGHT: - box.x = view->x + view->w - theme->title_height; - box.y = view->y - corner_square - INVISIBLE_MARGIN; - box.width = corner_square + INVISIBLE_MARGIN; - box.height = corner_square + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: - box.x = view->x + view->w - corner_square; - box.y = view->y + view->h - corner_square; - box.width = corner_square + INVISIBLE_MARGIN; - box.height = corner_square + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_CORNER_BOTTOM_LEFT: - box.x = view->x - theme->border_width - INVISIBLE_MARGIN; - box.y = view->y + view->h - corner_square; - box.width = corner_square + INVISIBLE_MARGIN; - box.height = corner_square + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_TOP: - box.x = view->x + theme->title_height; - box.y = view->y - corner_square - INVISIBLE_MARGIN; - box.width = view->w - 2 * theme->title_height; - box.height = theme->border_width + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_RIGHT: - box.x = view->x + view->w; - box.y = view->y; - box.width = theme->border_width + INVISIBLE_MARGIN; - box.height = view->h; - break; - case LAB_SSD_PART_BOTTOM: - box.x = view->x - theme->border_width; - box.y = view->y + view->h; - box.width = view->w + 2 * theme->border_width; - box.height = theme->border_width + INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_LEFT: - box.x = view->x - theme->border_width - INVISIBLE_MARGIN; - box.y = view->y; - box.width = theme->border_width + INVISIBLE_MARGIN; - box.height = view->h; - break; - case LAB_SSD_CLIENT: - box.x = view->x; - box.y = view->y; - box.width = view->w; - box.height = view->h; - break; - default: - break; - } - return box; -} - -static void -center_vertically(struct wlr_box *box, struct wlr_texture *texture) -{ - if (!texture) { - return; - } - box->y += (box->height - texture->height) / 2; -} - -static void -center_horizontally(struct view *view, struct wlr_box *box, - struct wlr_texture *texture) -{ - if (!texture) { - return; - } - box->x = view->x + (view->w - texture->width) / 2; -} - -static void -justify_right(struct view *view, struct wlr_box *box, - struct wlr_texture *texture) -{ - if (!texture) { - return; - } - box->x = view->x + (box->width - texture->width); -} - -bool -ssd_is_button(enum ssd_part_type type) -{ - return type == LAB_SSD_BUTTON_CLOSE || - type == LAB_SSD_BUTTON_MAXIMIZE || - type == LAB_SSD_BUTTON_ICONIFY || - type == LAB_SSD_BUTTON_WINDOW_MENU; -} - -struct wlr_box -ssd_visible_box(struct view *view, enum ssd_part_type type) -{ - struct theme *theme = view->server->theme; - struct wlr_box box = { 0 }; - switch (type) { - case LAB_SSD_BUTTON_CLOSE: - box = ssd_box(view, type); - break; - case LAB_SSD_BUTTON_MAXIMIZE: - box = ssd_box(view, type); - break; - case LAB_SSD_BUTTON_ICONIFY: - box = ssd_box(view, type); - break; - case LAB_SSD_BUTTON_WINDOW_MENU: - box = ssd_box(view, type); - break; - case LAB_SSD_PART_TITLEBAR: - box = ssd_box(view, type); - box.x += theme->title_height; - box.width -= 2 * theme->title_height; - break; - case LAB_SSD_PART_TITLE: - box = ssd_box(view, type); - center_vertically(&box, view->title.active); - if (theme->window_label_text_justify == LAB_JUSTIFY_CENTER) { - center_horizontally(view, &box, view->title.active); - } else if (theme->window_label_text_justify == LAB_JUSTIFY_RIGHT) { - justify_right(view, &box, view->title.active); - } - if (view->title.active) { - box.width = view->title.active->width; - box.height = view->title.active->height; - } - break; - case LAB_SSD_PART_CORNER_TOP_LEFT: - box = ssd_box(view, type); - box.x += INVISIBLE_MARGIN; - box.y += INVISIBLE_MARGIN; - box.width -= INVISIBLE_MARGIN; - box.height -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_CORNER_TOP_RIGHT: - box = ssd_box(view, type); - box.y += INVISIBLE_MARGIN; - box.width -= INVISIBLE_MARGIN; - box.height -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_TOP: - box = ssd_box(view, type); - box.y += INVISIBLE_MARGIN; - box.height -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_RIGHT: - box = ssd_box(view, type); - box.width -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_BOTTOM: - box = ssd_box(view, type); - box.height -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_LEFT: - box = ssd_box(view, type); - box.x += INVISIBLE_MARGIN; - box.width -= INVISIBLE_MARGIN; - break; - case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: - case LAB_SSD_PART_CORNER_BOTTOM_LEFT: - default: - break; - } - return box; -} - -enum ssd_part_type -ssd_at(struct view *view, double lx, double ly) -{ - enum ssd_part_type type; - for (type = 0; type < LAB_SSD_END_MARKER; ++type) { - struct wlr_box box = ssd_box(view, type); - if (wlr_box_contains_point(&box, lx, ly)) { - return type; - } - } - return LAB_SSD_NONE; -} - -uint32_t -ssd_resize_edges(enum ssd_part_type type) -{ - switch (type) { - case LAB_SSD_PART_TOP: - return WLR_EDGE_TOP; - case LAB_SSD_PART_RIGHT: - return WLR_EDGE_RIGHT; - case LAB_SSD_PART_BOTTOM: - return WLR_EDGE_BOTTOM; - case LAB_SSD_PART_LEFT: - return WLR_EDGE_LEFT; - case LAB_SSD_PART_CORNER_TOP_LEFT: - return WLR_EDGE_TOP | WLR_EDGE_LEFT; - case LAB_SSD_PART_CORNER_TOP_RIGHT: - return WLR_EDGE_RIGHT | WLR_EDGE_TOP; - case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: - return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; - case LAB_SSD_PART_CORNER_BOTTOM_LEFT: - return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; - default: - break; - } - return 0; -} - - -static struct ssd_part * -add_part(struct view *view, enum ssd_part_type type) -{ - struct ssd_part *part = calloc(1, sizeof(struct ssd_part)); - part->type = type; - wl_list_insert(&view->ssd.parts, &part->link); - return part; -} - -void -ssd_update_title(struct view *view) -{ - struct theme *theme = view->server->theme; - - struct font font = { - .name = rc.font_name_activewindow, - .size = rc.font_size_activewindow, - }; - - struct ssd_part *part; - wl_list_for_each(part, &view->ssd.parts, link) { - if (part->type == LAB_SSD_PART_TITLE) { - part->box = ssd_box(view, part->type); - break; - } - } - if (part->type != LAB_SSD_PART_TITLE) { - return; - } - - int max_width = part->box.width > 0 ? part->box.width : 1000; - - font_texture_create(view->server, &view->title.active, max_width, - view_get_string_prop(view, "title"), - &font, theme->window_active_label_text_color); - - font_texture_create(view->server, &view->title.inactive, max_width, - view_get_string_prop(view, "title"), - &font, theme->window_inactive_label_text_color); - - part->box = ssd_visible_box(view, part->type); -} - -void -ssd_create(struct view *view) -{ - struct theme *theme = view->server->theme; - struct ssd_part *part; - - view->ssd.box.x = view->x; - view->ssd.box.y = view->y; - view->ssd.box.width = view->w; - view->ssd.box.height = view->h; - - /* border */ - enum ssd_part_type border[4] = { - LAB_SSD_PART_TOP, - LAB_SSD_PART_RIGHT, - LAB_SSD_PART_BOTTOM, - LAB_SSD_PART_LEFT, - }; - for (int i = 0; i < 4; i++) { - part = add_part(view, border[i]); - part->box = ssd_visible_box(view, border[i]); - part->color.active = theme->window_active_border_color; - part->color.inactive = theme->window_inactive_border_color; - } - - /* titlebar */ - part = add_part(view, LAB_SSD_PART_TITLEBAR); - part->box = ssd_visible_box(view, LAB_SSD_PART_TITLEBAR); - part->color.active = theme->window_active_title_bg_color; - part->color.inactive = theme->window_inactive_title_bg_color; - - /* titlebar top-left corner */ - part = add_part(view, LAB_SSD_PART_CORNER_TOP_LEFT); - part->box = ssd_visible_box(view, part->type); - part->texture.active = &theme->corner_top_left_active_normal; - part->texture.inactive = &theme->corner_top_left_inactive_normal; - - /* titlebar top-right corner */ - part = add_part(view, LAB_SSD_PART_CORNER_TOP_RIGHT); - part->box = ssd_visible_box(view, part->type); - part->texture.active = &theme->corner_top_right_active_normal; - part->texture.inactive = &theme->corner_top_right_inactive_normal; - - /* title text */ - part = add_part(view, LAB_SSD_PART_TITLE); - ssd_update_title(view); - part->texture.active = &view->title.active; - part->texture.inactive = &view->title.inactive; -} - -void -ssd_destroy(struct view *view) -{ - struct ssd_part *part, *next; - wl_list_for_each_safe(part, next, &view->ssd.parts, link) { - wl_list_remove(&part->link); - free(part); - } -} - -static bool -geometry_changed(struct view *view) -{ - return view->x != view->ssd.box.x || view->y != view->ssd.box.y || - view->w != view->ssd.box.width || - view->h != view->ssd.box.height; -} - -void -ssd_update_geometry(struct view *view, bool force) -{ - if (!geometry_changed(view) && !force) { - return; - } - struct ssd_part *part; - wl_list_for_each(part, &view->ssd.parts, link) { - part->box = ssd_visible_box(view, part->type); - } - view->ssd.box.x = view->x; - view->ssd.box.y = view->y; - view->ssd.box.width = view->w; - view->ssd.box.height = view->h; - damage_all_outputs(view->server); -} - -bool -ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate) -{ - if (whole == candidate) { - return true; - } - if (whole == LAB_SSD_PART_TITLEBAR) { - return candidate >= LAB_SSD_BUTTON_CLOSE && candidate <= LAB_SSD_PART_TITLE; - } - if (whole == LAB_SSD_FRAME) { - return candidate >= LAB_SSD_BUTTON_CLOSE && candidate <= LAB_SSD_CLIENT; - } - if (whole == LAB_SSD_PART_TOP) { - return candidate == LAB_SSD_PART_CORNER_TOP_LEFT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; - } - if (whole == LAB_SSD_PART_RIGHT) { - return candidate == LAB_SSD_PART_CORNER_TOP_RIGHT || candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT; - } - if (whole == LAB_SSD_PART_BOTTOM) { - return candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; - } - if (whole == LAB_SSD_PART_LEFT) { - return candidate == LAB_SSD_PART_CORNER_TOP_LEFT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; - } - return false; -} diff --git a/src/ssd/meson.build b/src/ssd/meson.build new file mode 100644 index 00000000..95395419 --- /dev/null +++ b/src/ssd/meson.build @@ -0,0 +1,7 @@ +labwc_sources += files( + 'ssd.c', + 'ssd_part.c', + 'ssd_titlebar.c', + 'ssd_border.c', + 'ssd_extents.c', +) diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c new file mode 100644 index 00000000..bc86ca61 --- /dev/null +++ b/src/ssd/ssd.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Helpers for view server side decorations + * + * Copyright (C) Johan Malm 2020-2021 + */ + +#include +#include "config/rcxml.h" +#include "common/font.h" +#include "labwc.h" +#include "theme.h" +#include "ssd.h" + +/* TODO: use theme->title_height instead of SSD_HEIGHT */ +struct border +ssd_thickness(struct view *view) +{ + struct theme *theme = view->server->theme; + struct border border = { + .top = SSD_HEIGHT, + .bottom = theme->border_width, + .left = theme->border_width, + .right = theme->border_width, + }; + return border; +} + +struct wlr_box +ssd_max_extents(struct view *view) +{ + struct border border = ssd_thickness(view); + struct wlr_box box = { + .x = view->x - border.left, + .y = view->y - border.top, + .width = view->w + border.left + border.right, + .height = view->h + border.top + border.bottom, + }; + return box; +} + +bool +ssd_is_button(enum ssd_part_type type) +{ + return type == LAB_SSD_BUTTON_CLOSE + || type == LAB_SSD_BUTTON_MAXIMIZE + || type == LAB_SSD_BUTTON_ICONIFY + || type == LAB_SSD_BUTTON_WINDOW_MENU; +} + +enum ssd_part_type +ssd_get_part_type(struct view *view, struct wlr_scene_node *node) +{ + if (!node) { + return LAB_SSD_NONE; + } else if (node->type == WLR_SCENE_NODE_SURFACE) { + return LAB_SSD_CLIENT; + } else if (!view->ssd.tree) { + return LAB_SSD_NONE; + } + + struct wl_list *part_list = NULL; + struct wlr_scene_node *grandparent = + node->parent ? node->parent->parent : NULL; + + /* active titlebar */ + if (node->parent == &view->ssd.titlebar.active.tree->node) { + part_list = &view->ssd.titlebar.active.parts; + } else if (grandparent == &view->ssd.titlebar.active.tree->node) { + part_list = &view->ssd.titlebar.active.parts; + + /* extents */ + } else if (node->parent == &view->ssd.extents.tree->node) { + part_list = &view->ssd.extents.parts; + + /* active border */ + } else if (node->parent == &view->ssd.border.active.tree->node) { + part_list = &view->ssd.border.active.parts; + + /* inactive titlebar */ + } else if (node->parent == &view->ssd.titlebar.inactive.tree->node) { + part_list = &view->ssd.titlebar.inactive.parts; + } else if (grandparent == &view->ssd.titlebar.inactive.tree->node) { + part_list = &view->ssd.titlebar.inactive.parts; + + /* inactive border */ + } else if (node->parent == &view->ssd.border.inactive.tree->node) { + part_list = &view->ssd.border.inactive.parts; + } + + if (part_list) { + struct ssd_part *part; + wl_list_for_each(part, part_list, link) { + if (node == part->node) { + return part->type; + } + } + } + return LAB_SSD_NONE; +} + +enum ssd_part_type +ssd_at(struct view *view, double lx, double ly) +{ + double sx, sy; + struct wlr_scene_node *node = wlr_scene_node_at( + &view->server->scene->node, lx, ly, &sx, &sy); + return ssd_get_part_type(view, node); +} + +uint32_t +ssd_resize_edges(enum ssd_part_type type) +{ + switch (type) { + case LAB_SSD_PART_TOP: + return WLR_EDGE_TOP; + case LAB_SSD_PART_RIGHT: + return WLR_EDGE_RIGHT; + case LAB_SSD_PART_BOTTOM: + return WLR_EDGE_BOTTOM; + case LAB_SSD_PART_LEFT: + return WLR_EDGE_LEFT; + case LAB_SSD_PART_CORNER_TOP_LEFT: + return WLR_EDGE_TOP | WLR_EDGE_LEFT; + case LAB_SSD_PART_CORNER_TOP_RIGHT: + return WLR_EDGE_RIGHT | WLR_EDGE_TOP; + case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: + return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + case LAB_SSD_PART_CORNER_BOTTOM_LEFT: + return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + default: + break; + } + return 0; +} + +static void +_ssd_set_active(struct ssd *ssd, bool active) +{ + wlr_scene_node_set_enabled(&ssd->border.active.tree->node, active); + wlr_scene_node_set_enabled(&ssd->titlebar.active.tree->node, active); + wlr_scene_node_set_enabled(&ssd->border.inactive.tree->node, !active); + wlr_scene_node_set_enabled(&ssd->titlebar.inactive.tree->node, !active); +} + +void +ssd_create(struct view *view) +{ + if (view->ssd.tree) { + /* SSD was hidden. Just enable it */ + wlr_scene_node_set_enabled(&view->ssd.tree->node, true); + return; + } + + view->ssd.tree = wlr_scene_tree_create(&view->scene_tree->node); + wlr_scene_node_lower_to_bottom(&view->ssd.tree->node); + ssd_extents_create(view); + ssd_titlebar_create(view); + ssd_border_create(view); +} + +void +ssd_update_geometry(struct view *view) +{ + /* TODO: verify we are not called without reason. like in commit handlers */ + if (!view->ssd.tree || !view->scene_node) { + return; + } + + if (view->ssd.enabled && !view->ssd.tree->node.state.enabled) { + wlr_scene_node_set_enabled(&view->ssd.tree->node, true); + } + if (!view->ssd.enabled && view->ssd.tree->node.state.enabled) { + wlr_scene_node_set_enabled(&view->ssd.tree->node, false); + } + + int width = view->w; + int height = view->h; + if (width == view->ssd.state.width && height == view->ssd.state.height) { + return; + } + ssd_extents_update(view); + ssd_border_update(view); + ssd_titlebar_update(view); + + view->ssd.state.width = width; + view->ssd.state.height = height; +} + +void +ssd_hide(struct view *view) +{ + if (!view->ssd.tree) { + return; + } + + wlr_log(WLR_ERROR, "Hiding SSD"); + wlr_scene_node_set_enabled(&view->ssd.tree->node, false); +} + +void ssd_reload(struct view *view) +{ + if (!view->ssd.tree) { + return; + } + + bool view_was_active = view->server->ssd_focused_view == view; + ssd_destroy(view); + ssd_create(view); + if (view_was_active) { + view->server->ssd_focused_view = view; + } else { + _ssd_set_active(&view->ssd, false); + } +} + +void +ssd_destroy(struct view *view) +{ + if (!view->ssd.tree) { + return; + } + + /* Maybe reset focused view */ + if (view->server->ssd_focused_view == view) { + view->server->ssd_focused_view = NULL; + } + + /* Maybe reset hover view */ + struct ssd_hover_state *hover_state; + hover_state = &view->server->ssd_hover_state; + if (hover_state->view == view) { + hover_state->view = NULL; + hover_state->type = LAB_SSD_NONE; + hover_state->node = NULL; + } + + /* Destroy subcomponents */ + ssd_titlebar_destroy(view); + ssd_border_destroy(view); + ssd_extents_destroy(view); + wlr_scene_node_destroy(&view->ssd.tree->node); + view->ssd.tree = NULL; +} + +bool +ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate) +{ + if (whole == candidate) { + return true; + } + if (whole == LAB_SSD_PART_TITLEBAR) { + return candidate >= LAB_SSD_BUTTON_CLOSE + && candidate <= LAB_SSD_PART_TITLE; + } + if (whole == LAB_SSD_FRAME) { + return candidate >= LAB_SSD_BUTTON_CLOSE + && candidate <= LAB_SSD_CLIENT; + } + if (whole == LAB_SSD_PART_TOP) { + return candidate == LAB_SSD_PART_CORNER_TOP_LEFT + || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; + } + if (whole == LAB_SSD_PART_RIGHT) { + return candidate == LAB_SSD_PART_CORNER_TOP_RIGHT + || candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT; + } + if (whole == LAB_SSD_PART_BOTTOM) { + return candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT + || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; + } + if (whole == LAB_SSD_PART_LEFT) { + return candidate == LAB_SSD_PART_CORNER_TOP_LEFT + || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; + } + return false; +} + +void +ssd_set_active(struct view *view) +{ + if (!view->ssd.tree) { + return; + } + + struct view *last = view->server->ssd_focused_view; + if (last == view) { + return; + } + if (last && last->ssd.tree) { + _ssd_set_active(&last->ssd, false); + } + _ssd_set_active(&view->ssd, true); + view->server->ssd_focused_view = view; +} diff --git a/src/ssd/ssd_border.c b/src/ssd/ssd_border.c new file mode 100644 index 00000000..104b5c5c --- /dev/null +++ b/src/ssd/ssd_border.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "labwc.h" +#include "ssd.h" +#include "theme.h" +#include "common/scene-helpers.h" + +#define FOR_EACH_STATE(view, tmp) FOR_EACH(tmp, \ + &(view)->ssd.border.active, \ + &(view)->ssd.border.inactive) + +void +ssd_border_create(struct view *view) +{ + struct theme *theme = view->server->theme; + int width = view->w; + int height = view->h; + int full_width = width + 2 * theme->border_width; + + float *color; + struct wlr_scene_node *parent; + struct ssd_sub_tree *subtree; + + FOR_EACH_STATE(view, subtree) { + subtree->tree = wlr_scene_tree_create(&view->ssd.tree->node); + parent = &subtree->tree->node; + wlr_scene_node_set_position(parent, -theme->border_width, 0); + if (subtree == &view->ssd.border.active) { + color = theme->window_active_border_color; + } else { + color = theme->window_inactive_border_color; + wlr_scene_node_set_enabled(parent, false); + } + wl_list_init(&subtree->parts); + add_scene_rect(&subtree->parts, LAB_SSD_PART_LEFT, parent, + theme->border_width, height, 0, 0, color); + add_scene_rect(&subtree->parts, LAB_SSD_PART_RIGHT, parent, + theme->border_width, height, + theme->border_width + width, 0, color); + add_scene_rect(&subtree->parts, LAB_SSD_PART_BOTTOM, parent, + full_width, theme->border_width, + 0, height, color); + add_scene_rect(&subtree->parts, LAB_SSD_PART_TOP, parent, + full_width - 2 * BUTTON_WIDTH, theme->border_width, + BUTTON_WIDTH, -SSD_HEIGHT, color); + } FOR_EACH_END +} + +void +ssd_border_update(struct view *view) +{ + struct theme *theme = view->server->theme; + + int width = view->w; + int height = view->h; + int full_width = width + 2 * theme->border_width; + + struct ssd_part *part; + struct wlr_scene_rect *rect; + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + wl_list_for_each(part, &subtree->parts, link) { + rect = lab_wlr_scene_get_rect(part->node); + switch (part->type) { + case LAB_SSD_PART_LEFT: + wlr_scene_rect_set_size(rect, theme->border_width, height); + continue; + case LAB_SSD_PART_RIGHT: + wlr_scene_rect_set_size(rect, theme->border_width, height); + wlr_scene_node_set_position( + part->node, theme->border_width + width, 0); + continue; + case LAB_SSD_PART_BOTTOM: + wlr_scene_rect_set_size(rect, full_width, theme->border_width); + wlr_scene_node_set_position(part->node, 0, height); + continue; + case LAB_SSD_PART_TOP: + wlr_scene_rect_set_size(rect, + full_width - 2 * BUTTON_WIDTH, + theme->border_width); + wlr_scene_node_set_position(part->node, + BUTTON_WIDTH, -SSD_HEIGHT); + continue; + default: + continue; + } + } + } FOR_EACH_END +} + +void +ssd_border_destroy(struct view *view) +{ + if (!view->ssd.border.active.tree) { + return; + } + + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + ssd_destroy_parts(&subtree->parts); + wlr_scene_node_destroy(&subtree->tree->node); + subtree->tree = NULL; + } FOR_EACH_END +} + +#undef FOR_EACH_STATE diff --git a/src/ssd/ssd_extents.c b/src/ssd/ssd_extents.c new file mode 100644 index 00000000..b80753c4 --- /dev/null +++ b/src/ssd/ssd_extents.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "labwc.h" +#include "ssd.h" +#include "theme.h" +#include "common/scene-helpers.h" + +void +ssd_extents_create(struct view *view) +{ + struct theme *theme = view->server->theme; + float invisible[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + struct wl_list *part_list = &view->ssd.extents.parts; + int width = view->w; + int height = view->h; + int full_height = height + theme->border_width + SSD_HEIGHT; + int full_width = width + 2 * theme->border_width; + int extended_area = EXTENDED_AREA; + + view->ssd.extents.tree = wlr_scene_tree_create(&view->ssd.tree->node); + struct wlr_scene_node *parent = &view->ssd.extents.tree->node; + if (view->maximized || view->fullscreen) { + wlr_scene_node_set_enabled(parent, false); + } + wl_list_init(&view->ssd.extents.parts); + wlr_scene_node_set_position(parent, + -(theme->border_width + extended_area), -(SSD_HEIGHT + extended_area)); + + /* Top */ + add_scene_rect(part_list, LAB_SSD_PART_CORNER_TOP_LEFT, parent, + extended_area, extended_area, + 0, 0, invisible); + add_scene_rect(part_list, LAB_SSD_PART_TOP, parent, + full_width, extended_area, + extended_area, 0, invisible); + add_scene_rect(part_list, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, + extended_area, extended_area, + extended_area + full_width, 0, invisible); + + /* Sides */ + add_scene_rect(part_list, LAB_SSD_PART_LEFT, parent, + extended_area, full_height, + 0, extended_area, invisible); + add_scene_rect(part_list, LAB_SSD_PART_RIGHT, parent, + extended_area, full_height, + extended_area + full_width, extended_area, invisible); + + /* Bottom */ + add_scene_rect(part_list, LAB_SSD_PART_CORNER_BOTTOM_LEFT, parent, + extended_area, extended_area, + 0, extended_area + full_height, invisible); + add_scene_rect(part_list, LAB_SSD_PART_BOTTOM, parent, + full_width, extended_area, + extended_area, extended_area + full_height, invisible); + add_scene_rect(part_list, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent, + extended_area, extended_area, + extended_area + full_width, extended_area + full_height, invisible); +} + +void +ssd_extents_update(struct view *view) +{ + if (view->maximized || view->fullscreen) { + wlr_scene_node_set_enabled(&view->ssd.extents.tree->node, false); + return; + } + if (!view->ssd.extents.tree->node.state.enabled) { + wlr_scene_node_set_enabled(&view->ssd.extents.tree->node, true); + } + + struct theme *theme = view->server->theme; + + int width = view->w; + int height = view->h; + int full_height = height + theme->border_width + SSD_HEIGHT; + int full_width = width + 2 * theme->border_width; + int extended_area = EXTENDED_AREA; + + struct ssd_part *part; + struct wlr_scene_rect *rect; + wl_list_for_each(part, &view->ssd.extents.parts, link) { + rect = lab_wlr_scene_get_rect(part->node); + switch (part->type) { + case LAB_SSD_PART_TOP: + wlr_scene_rect_set_size(rect, full_width, extended_area); + continue; + case LAB_SSD_PART_CORNER_TOP_RIGHT: + wlr_scene_node_set_position( + part->node, extended_area + full_width, 0); + continue; + case LAB_SSD_PART_LEFT: + wlr_scene_rect_set_size(rect, extended_area, full_height); + continue; + case LAB_SSD_PART_RIGHT: + wlr_scene_rect_set_size(rect, extended_area, full_height); + wlr_scene_node_set_position( + part->node, extended_area + full_width, extended_area); + continue; + case LAB_SSD_PART_CORNER_BOTTOM_LEFT: + wlr_scene_node_set_position( + part->node, 0, extended_area + full_height); + continue; + case LAB_SSD_PART_BOTTOM: + wlr_scene_rect_set_size(rect, full_width, extended_area); + wlr_scene_node_set_position( + part->node, extended_area, extended_area + full_height); + continue; + case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: + wlr_scene_node_set_position(part->node, + extended_area + full_width, extended_area + full_height); + continue; + default: + continue; + } + } +} + +void +ssd_extents_destroy(struct view *view) +{ + if (!view->ssd.extents.tree) { + return; + } + + ssd_destroy_parts(&view->ssd.extents.parts); + wlr_scene_node_destroy(&view->ssd.extents.tree->node); + view->ssd.extents.tree = NULL; +} diff --git a/src/ssd/ssd_part.c b/src/ssd/ssd_part.c new file mode 100644 index 00000000..f6e55ba1 --- /dev/null +++ b/src/ssd/ssd_part.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include "labwc.h" +#include "ssd.h" + +struct ssd_part * +add_scene_part(struct wl_list *part_list, enum ssd_part_type type) +{ + struct ssd_part *part = calloc(1, sizeof(struct ssd_part)); + part->type = type; + wl_list_insert(part_list->prev, &part->link); + return part; +} + +struct ssd_part * +add_scene_rect(struct wl_list *list, enum ssd_part_type type, + struct wlr_scene_node *parent, int width, int height, + int x, int y, float color[4]) +{ + /* + * When initialized without surface being mapped, + * size may be negative. Just set to 0, next call + * to ssd_*_update() will update the rect to use + * its correct size. + */ + width = width >= 0 ? width : 0; + height = height >= 0 ? height : 0; + + struct ssd_part *part = add_scene_part(list, type); + part->node = &wlr_scene_rect_create( + parent, width, height, color)->node; + wlr_scene_node_set_position(part->node, x, y); + return part; +} + +struct ssd_part * +add_scene_buffer(struct wl_list *list, enum ssd_part_type type, + struct wlr_scene_node *parent, struct wlr_buffer *buffer, + int x, int y) +{ + struct ssd_part *part = add_scene_part(list, type); + part->node = &wlr_scene_buffer_create(parent, buffer)->node; + wlr_scene_node_set_position(part->node, x, y); + return part; +} + +static void +finish_scene_button(struct wl_list *part_list, enum ssd_part_type type, + struct wlr_scene_node *parent, struct wlr_buffer *icon_buffer) +{ + float hover_bg[4] = {0.15f, 0.15f, 0.15f, 0.3f}; + + /* Icon */ + add_scene_buffer(part_list, type, parent, icon_buffer, + (BUTTON_WIDTH - icon_buffer->width) / 2, + (SSD_HEIGHT - icon_buffer->height) / 2); + + /* Hover overlay */ + struct ssd_part *hover_part; + hover_part = add_scene_rect(part_list, type, parent, + BUTTON_WIDTH, SSD_HEIGHT, 0, 0, hover_bg); + wlr_scene_node_set_enabled(hover_part->node, false); +} + +struct ssd_part * +add_scene_button_corner(struct wl_list *part_list, enum ssd_part_type type, + struct wlr_scene_node *parent, struct wlr_buffer *corner_buffer, + struct wlr_buffer *icon_buffer, int x) +{ + struct ssd_part *part; + /* Background */ + part = add_scene_buffer(part_list, type, parent, corner_buffer, x, 0); + finish_scene_button(part_list, type, part->node, icon_buffer); + return part; +} + +struct ssd_part * +add_scene_button(struct wl_list *part_list, enum ssd_part_type type, + struct wlr_scene_node *parent, float *bg_color, + struct wlr_buffer *icon_buffer, int x) +{ + struct ssd_part *part; + /* Background */ + part = add_scene_rect(part_list, type, parent, + BUTTON_WIDTH, SSD_HEIGHT, x, 0, bg_color); + finish_scene_button(part_list, type, part->node, icon_buffer); + return part; +} + +struct ssd_part * +ssd_get_part(struct wl_list *part_list, enum ssd_part_type type) +{ + struct ssd_part *part; + wl_list_for_each(part, part_list, link) { + if (part->type == type) { + return part; + } + } + return NULL; +} + +void +ssd_destroy_parts(struct wl_list *list) +{ + struct ssd_part *part, *tmp; + wl_list_for_each_reverse_safe(part, tmp, list, link) { + if (part->node) { + wlr_scene_node_destroy(part->node); + } + if (part->buffer) { + wlr_buffer_drop(&part->buffer->base); + } + wl_list_remove(&part->link); + free(part); + } + assert(wl_list_empty(list)); +} diff --git a/src/ssd/ssd_titlebar.c b/src/ssd/ssd_titlebar.c new file mode 100644 index 00000000..1c7d9876 --- /dev/null +++ b/src/ssd/ssd_titlebar.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define _POSIX_C_SOURCE 200809L +#include +#include +#include "labwc.h" +#include "ssd.h" +#include "theme.h" +#include "common/font.h" +#include "common/scene-helpers.h" + +#define FOR_EACH_STATE(view, tmp) FOR_EACH(tmp, \ + &(view)->ssd.titlebar.active, \ + &(view)->ssd.titlebar.inactive) + +void +ssd_titlebar_create(struct view *view) +{ + struct theme *theme = view->server->theme; + int width = view->w; + int full_width = width + 2 * theme->border_width; + + float *color; + struct wlr_scene_node *parent; + struct wlr_buffer *corner_top_left; + struct wlr_buffer *corner_top_right; + + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + subtree->tree = wlr_scene_tree_create(&view->ssd.tree->node); + parent = &subtree->tree->node; + wlr_scene_node_set_position(parent, -theme->border_width, -SSD_HEIGHT); + if (subtree == &view->ssd.titlebar.active) { + color = theme->window_active_title_bg_color; + corner_top_left = &theme->corner_top_left_active_normal->base; + corner_top_right = &theme->corner_top_right_active_normal->base; + } else { + color = theme->window_inactive_title_bg_color; + corner_top_left = &theme->corner_top_left_inactive_normal->base; + corner_top_right = &theme->corner_top_right_inactive_normal->base; + wlr_scene_node_set_enabled(parent, false); + } + wl_list_init(&subtree->parts); + + /* Title */ + add_scene_rect(&subtree->parts, LAB_SSD_PART_TITLEBAR, parent, + full_width - BUTTON_WIDTH * BUTTON_COUNT, SSD_HEIGHT, + BUTTON_WIDTH, 0, color); + /* Buttons */ + add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_WINDOW_MENU, + parent, corner_top_left, + &theme->xbm_menu_active_unpressed->base, 0); + add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, + color, &theme->xbm_iconify_active_unpressed->base, + full_width - BUTTON_WIDTH * 3); + add_scene_button(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, + color, &theme->xbm_maximize_active_unpressed->base, + full_width - BUTTON_WIDTH * 2); + add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, parent, + corner_top_right, &theme->xbm_close_active_unpressed->base, + full_width - BUTTON_WIDTH * 1); + } FOR_EACH_END + ssd_update_title(view); +} + +static bool +is_direct_child(struct wlr_scene_node *node, struct ssd_sub_tree *subtree) +{ + return node->parent == &subtree->tree->node; +} + +void +ssd_titlebar_update(struct view *view) +{ + int width = view->w; + if (width == view->ssd.state.width) { + return; + } + int full_width = width + 2 * view->server->theme->border_width; + + struct ssd_part *part; + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + wl_list_for_each(part, &subtree->parts, link) { + switch (part->type) { + case LAB_SSD_PART_TITLEBAR: + wlr_scene_rect_set_size(lab_wlr_scene_get_rect(part->node), + full_width - BUTTON_WIDTH * BUTTON_COUNT, SSD_HEIGHT); + continue; + case LAB_SSD_BUTTON_ICONIFY: + if (is_direct_child(part->node, subtree)) { + wlr_scene_node_set_position(part->node, + full_width - BUTTON_WIDTH * 3, 0); + } + continue; + case LAB_SSD_BUTTON_MAXIMIZE: + if (is_direct_child(part->node, subtree)) { + wlr_scene_node_set_position(part->node, + full_width - BUTTON_WIDTH * 2, 0); + } + continue; + case LAB_SSD_BUTTON_CLOSE: + if (is_direct_child(part->node, subtree)) { + wlr_scene_node_set_position(part->node, + full_width - BUTTON_WIDTH * 1, 0); + } + continue; + default: + continue; + } + } + } FOR_EACH_END + ssd_update_title(view); +} + +void +ssd_titlebar_destroy(struct view *view) +{ + if (!view->ssd.titlebar.active.tree) { + return; + } + + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + ssd_destroy_parts(&subtree->parts); + wlr_scene_node_destroy(&subtree->tree->node); + subtree->tree = NULL; + } FOR_EACH_END + + if (view->ssd.state.title.text) { + free(view->ssd.state.title.text); + view->ssd.state.title.text = NULL; + } +} + +/* + * For ssd_update_title* we do not early out because + * .active and .inactive may result in different sizes + * of the title (font family/size) or background of + * the title (different button/border width). + */ + +static void +ssd_update_title_positions(struct view *view) +{ + struct theme *theme = view->server->theme; + int width = view->w; + int full_width = width + 2 * view->server->theme->border_width; + + int x, y; + struct wlr_scene_rect *rect; + struct ssd_part *part; + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLE); + if (!part) { + /* view->surface never been mapped */ + continue; + } + + x = 0; + y = (SSD_HEIGHT - part->buffer->base.height) / 2; + rect = lab_wlr_scene_get_rect(part->node->parent); + if (rect->width <= 0) { + wlr_scene_node_set_position(part->node, x, y); + continue; + } + + if (theme->window_label_text_justify == LAB_JUSTIFY_CENTER) { + if (part->buffer->base.width + BUTTON_WIDTH * 2 <= rect->width) { + /* Center based on the full width */ + x = (full_width - part->buffer->base.width) / 2; + x -= BUTTON_WIDTH; + } else { + /* + * Center based on the width between the buttons. + * Title jumps around once this is hit but its still + * better than to hide behind the buttons on the right. + */ + x = (rect->width - part->buffer->base.width) / 2; + } + } else if (theme->window_label_text_justify == LAB_JUSTIFY_RIGHT) { + x = rect->width - part->buffer->base.width; + } else if (theme->window_label_text_justify == LAB_JUSTIFY_LEFT) { + /* TODO: maybe add some theme x padding here? */ + } + wlr_scene_node_set_position(part->node, x, y); + } FOR_EACH_END +} + +void +ssd_update_title(struct view *view) +{ + if (!view->ssd.tree) { + return; + } + + char *title = (char *)view_get_string_prop(view, "title"); + if (!title || !*title) { + return; + } + + struct theme *theme = view->server->theme; + struct ssd_state_title *state = &view->ssd.state.title; + bool title_unchanged = state->text && !strcmp(title, state->text); + + /* TODO: Do we only have active window fonts? */ + struct font font = { + .name = rc.font_name_activewindow, + .size = rc.font_size_activewindow, + }; + + float *text_color; + struct wlr_scene_rect *rect; + struct ssd_part *part; + struct ssd_part *parent_part; + struct ssd_sub_tree *subtree; + struct ssd_state_title_width *dstate; + FOR_EACH_STATE(view, subtree) { + parent_part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR); + assert(parent_part); + + if (subtree == &view->ssd.titlebar.active) { + dstate = &state->active; + text_color = theme->window_active_label_text_color; + } else { + dstate = &state->inactive; + text_color = theme->window_inactive_label_text_color; + } + + rect = lab_wlr_scene_get_rect(parent_part->node); + if (rect->width <= 0) { + dstate->truncated = true; + continue; + } + + if (title_unchanged + && !dstate->truncated && dstate->width < rect->width) { + /* title the same + we don't need to resize title */ + continue; + } + + part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLE); + if (!part) { + part = add_scene_part(&subtree->parts, LAB_SSD_PART_TITLE); + } + if (part->node) { + wlr_scene_node_destroy(part->node); + } + font_buffer_update( + &part->buffer, rect->width, title, &font, text_color); + part->node = &wlr_scene_buffer_create( + parent_part->node, &part->buffer->base)->node; + dstate->width = part->buffer->base.width; + dstate->truncated = rect->width <= dstate->width; + } FOR_EACH_END + + if (!title_unchanged) { + if (state->text) { + free(state->text); + } + state->text = strdup(title); + } + ssd_update_title_positions(view); +} + +/* + * Returns the wlr_scene_node for hover effect. + * To disable the hover effect later on just call + * wlr_scene_node_set_enabled(node, false). + */ +struct wlr_scene_node * +ssd_button_hover_enable(struct view *view, enum ssd_part_type type) +{ + if (!view->ssd.tree) { + wlr_log(WLR_ERROR, "%s() for destroyed view", __func__); + return NULL; + } + + assert(ssd_is_button(type)); + struct ssd_part *part; + struct ssd_sub_tree *subtree; + FOR_EACH_STATE(view, subtree) { + if (subtree->tree->node.state.enabled) { + part = ssd_get_part(&subtree->parts, type); + if (!part) { + wlr_log(WLR_ERROR, "hover enable failed to find button"); + return NULL; + } + struct wlr_scene_node *child; + wl_list_for_each(child, &part->node->state.children, state.link) { + if (child->type == WLR_SCENE_NODE_RECT) { + wlr_scene_node_set_enabled(child, true); + return child; + } + } + } + } FOR_EACH_END + + wlr_log(WLR_ERROR, "hover enable failed to find button"); + return NULL; +} + +#undef FOR_EACH_STATE diff --git a/src/subsurface.c b/src/subsurface.c deleted file mode 100644 index 79866355..00000000 --- a/src/subsurface.c +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 the sway authors - * This file is only needed in support of tracking damage - */ - -#include "labwc.h" - -static void -subsurface_handle_destroy(struct wl_listener *listener, void *data) -{ - struct view_subsurface *subsurface = wl_container_of(listener, - subsurface, destroy); - struct view_child *view_child = (struct view_child *)subsurface; - if (!view_child) { - return; - } - wl_list_remove(&subsurface->destroy.link); - view_child_finish(&subsurface->view_child); - free(subsurface); -} - -void -view_subsurface_create(struct view *view, struct wlr_subsurface *wlr_subsurface) -{ - struct view_subsurface *subsurface = - calloc(1, sizeof(struct view_subsurface)); - if (!subsurface) { - return; - } - view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); - subsurface->subsurface = wlr_subsurface; - - subsurface->destroy.notify = subsurface_handle_destroy; - wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); -} diff --git a/src/theme.c b/src/theme.c index ec07f9b0..5a4a6e5f 100644 --- a/src/theme.c +++ b/src/theme.c @@ -25,6 +25,8 @@ #include "config/rcxml.h" #include "theme.h" #include "xbm/xbm.h" +#include "buffer.h" +#include "ssd.h" static int hex_to_dec(char c) @@ -336,8 +338,8 @@ set_source(cairo_t *cairo, float *c) cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]); } -static struct wlr_texture * -rounded_rect(struct wlr_renderer *renderer, struct rounded_corner_ctx *ctx) +static struct lab_data_buffer * +rounded_rect(struct rounded_corner_ctx *ctx) { /* 1 degree in radians (=2π/360) */ double deg = 0.017453292519943295; @@ -350,9 +352,12 @@ rounded_rect(struct wlr_renderer *renderer, struct rounded_corner_ctx *ctx) double h = ctx->box->height; double r = ctx->radius; - cairo_surface_t *surf = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); - cairo_t *cairo = cairo_create(surf); + struct lab_data_buffer *buffer; + /* TODO: scale */ + buffer = buffer_create_cairo(w, h, 1, true); + + cairo_t *cairo = buffer->cairo; + cairo_surface_t *surf = cairo_get_target(cairo); /* set transparent background */ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); @@ -405,28 +410,19 @@ rounded_rect(struct wlr_renderer *renderer, struct rounded_corner_ctx *ctx) wlr_log(WLR_ERROR, "unknown corner type"); } cairo_stroke(cairo); - - /* convert to wlr_texture */ cairo_surface_flush(surf); - unsigned char *data = cairo_image_surface_get_data(surf); - struct wlr_texture *texture = wlr_texture_from_pixels(renderer, - DRM_FORMAT_ARGB8888, cairo_image_surface_get_stride(surf), - w, h, data); - cairo_destroy(cairo); - cairo_surface_destroy(surf); - return texture; + return buffer; } static void -create_corners(struct theme *theme, struct wlr_renderer *renderer) +create_corners(struct theme *theme) { - int corner_square = theme->title_height + theme->border_width; struct wlr_box box = { .x = 0, .y = 0, - .width = corner_square, - .height = corner_square, + .width = BUTTON_WIDTH, + .height = SSD_HEIGHT, }; struct rounded_corner_ctx ctx = { @@ -437,20 +433,20 @@ create_corners(struct theme *theme, struct wlr_renderer *renderer) .border_color = theme->window_active_border_color, .corner = LAB_CORNER_TOP_LEFT, }; - theme->corner_top_left_active_normal = rounded_rect(renderer, &ctx); + theme->corner_top_left_active_normal = rounded_rect(&ctx); ctx.fill_color = theme->window_inactive_title_bg_color, ctx.border_color = theme->window_inactive_border_color, - theme->corner_top_left_inactive_normal = rounded_rect(renderer, &ctx); + theme->corner_top_left_inactive_normal = rounded_rect(&ctx); ctx.corner = LAB_CORNER_TOP_RIGHT; ctx.fill_color = theme->window_active_title_bg_color, ctx.border_color = theme->window_active_border_color, - theme->corner_top_right_active_normal = rounded_rect(renderer, &ctx); + theme->corner_top_right_active_normal = rounded_rect(&ctx); ctx.fill_color = theme->window_inactive_title_bg_color, ctx.border_color = theme->window_inactive_border_color, - theme->corner_top_right_inactive_normal = rounded_rect(renderer, &ctx); + theme->corner_top_right_inactive_normal = rounded_rect(&ctx); } static void @@ -480,8 +476,7 @@ post_processing(struct theme *theme) } void -theme_init(struct theme *theme, struct wlr_renderer *renderer, - const char *theme_name) +theme_init(struct theme *theme, const char *theme_name) { /* * Set some default values. This is particularly important on @@ -491,8 +486,8 @@ theme_init(struct theme *theme, struct wlr_renderer *renderer, theme_read(theme, theme_name); post_processing(theme); - create_corners(theme, renderer); - xbm_load(theme, renderer); + create_corners(theme); + xbm_load(theme); } void diff --git a/src/view-child.c b/src/view-child.c deleted file mode 100644 index 462a53a4..00000000 --- a/src/view-child.c +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 the sway authors - * This file is only needed in support of tracking damage - */ - -#include "labwc.h" - -static void -view_child_handle_commit(struct wl_listener *listener, void *data) -{ - struct view_child *child = wl_container_of(listener, child, commit); - damage_all_outputs(child->parent->server); -} - -static void -view_child_handle_new_subsurface(struct wl_listener *listener, void *data) -{ - struct view_child *child; - child = wl_container_of(listener, child, new_subsurface); - struct wlr_subsurface *wlr_subsurface = data; - view_subsurface_create(child->parent, wlr_subsurface); -} - -void -view_child_finish(struct view_child *child) -{ - wl_list_remove(&child->commit.link); - wl_list_remove(&child->new_subsurface.link); -} - -void -view_child_init(struct view_child *child, struct view *view, - struct wlr_surface *wlr_surface) -{ - child->parent = view; - child->surface = wlr_surface; - child->commit.notify = view_child_handle_commit; - wl_signal_add(&wlr_surface->events.commit, &child->commit); - child->new_subsurface.notify = view_child_handle_new_subsurface; - wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); -} diff --git a/src/view-impl.c b/src/view-impl.c index d10dd2a4..ff8f9e3f 100644 --- a/src/view-impl.c +++ b/src/view-impl.c @@ -12,6 +12,4 @@ view_impl_map(struct view *view) view_update_title(view); view_update_app_id(view); - - damage_all_outputs(view->server); } diff --git a/src/view.c b/src/view.c index 5587b1dc..53065948 100644 --- a/src/view.c +++ b/src/view.c @@ -7,6 +7,9 @@ void view_set_activated(struct view *view, bool activated) { + if (view->ssd.enabled) { + ssd_set_active(view); + } if (view->impl->set_activated) { view->impl->set_activated(view, activated); } @@ -31,6 +34,7 @@ view_move(struct view *view, double x, double y) view->impl->move(view, x, y); } view_discover_output(view); + wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); } void @@ -254,7 +258,7 @@ view_toggle_decorations(struct view *view) { if (!view->fullscreen) { view->ssd.enabled = !view->ssd.enabled; - ssd_update_geometry(view, true); + ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); } @@ -266,7 +270,7 @@ view_set_decorations(struct view *view, bool decorations) { if (view->ssd.enabled != decorations && !view->fullscreen) { view->ssd.enabled = decorations; - ssd_update_geometry(view, true); + ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); } @@ -341,24 +345,6 @@ view_adjust_for_layout_change(struct view *view) } } -void -view_for_each_surface(struct view *view, wlr_surface_iterator_func_t iterator, - void *user_data) -{ - if (view->impl->for_each_surface) { - view->impl->for_each_surface(view, iterator, user_data); - } -} - -void -view_for_each_popup_surface(struct view *view, - wlr_surface_iterator_func_t iterator, void *data) -{ - if (view->impl->for_each_popup_surface) { - view->impl->for_each_popup_surface(view, iterator, data); - } -} - struct border view_border(struct view *view) { @@ -371,27 +357,9 @@ view_border(struct view *view) return border; } -static void -surface_enter_for_each_surface(struct wlr_surface *surface, int sx, int sy, - void *user_data) -{ - struct wlr_output *wlr_output = user_data; - wlr_surface_send_enter(surface, wlr_output); -} - -static void -surface_leave_for_each_surface(struct wlr_surface *surface, int sx, int sy, - void *user_data) -{ - struct wlr_output *wlr_output = user_data; - wlr_surface_send_leave(surface, wlr_output); -} - static void view_output_enter(struct view *view, struct wlr_output *wlr_output) { - view_for_each_surface(view, surface_enter_for_each_surface, - wlr_output); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_enter( view->toplevel_handle, wlr_output); @@ -401,8 +369,6 @@ view_output_enter(struct view *view, struct wlr_output *wlr_output) static void view_output_leave(struct view *view, struct wlr_output *wlr_output) { - view_for_each_surface(view, surface_leave_for_each_surface, - wlr_output); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_leave( view->toplevel_handle, wlr_output); @@ -641,7 +607,6 @@ view_update_title(struct view *view) } ssd_update_title(view); wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title); - damage_all_outputs(view->server); } void diff --git a/src/xbm/xbm.c b/src/xbm/xbm.c index 35a97cfa..a251ed44 100644 --- a/src/xbm/xbm.c +++ b/src/xbm/xbm.c @@ -15,6 +15,7 @@ #include "theme.h" #include "xbm/parse.h" #include "xbm/xbm.h" +#include "buffer.h" /* built-in 6x6 buttons */ char menu_button_normal[] = { 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 }; @@ -23,28 +24,6 @@ char max_button_normal[] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }; char max_button_toggled[] = { 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f }; char close_button_normal[] = { 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 }; -static struct wlr_texture * -texture_from_pixmap(struct wlr_renderer *renderer, struct pixmap *pixmap) -{ - if (!pixmap) { - return NULL; - } - return wlr_texture_from_pixels(renderer, DRM_FORMAT_ARGB8888, - pixmap->width * 4, pixmap->width, - pixmap->height, pixmap->data); -} - -static struct wlr_texture * -texture_from_builtin(struct wlr_renderer *renderer, const char *button) -{ - struct pixmap pixmap = parse_xbm_builtin(button, 6); - struct wlr_texture *texture = texture_from_pixmap(renderer, &pixmap); - if (pixmap.data) { - free(pixmap.data); - } - return texture; -} - static char * xbm_path(const char *button) { @@ -55,62 +34,59 @@ xbm_path(const char *button) } static void -load_button(struct wlr_renderer *renderer, const char *filename, - struct wlr_texture **texture, char *button) +load_button(const char *filename, struct lab_data_buffer **buffer, char *button) { - if (*texture) { - wlr_texture_destroy(*texture); - *texture = NULL; + struct pixmap pixmap = {0}; + if (*buffer) { + wlr_buffer_drop(&(*buffer)->base); + *buffer = NULL; } /* Read file into memory as it's easier to tokenzie that way */ - char *buffer = grab_file(xbm_path(filename)); - if (!buffer) { - goto out; + char *token_buffer = grab_file(xbm_path(filename)); + if (token_buffer) { + struct token *tokens = tokenize_xbm(token_buffer); + free(token_buffer); + pixmap = parse_xbm_tokens(tokens); + if (tokens) { + free(tokens); + } + } + if (!pixmap.data) { + pixmap = parse_xbm_builtin(button, 6); } - struct token *tokens = tokenize_xbm(buffer); - free(buffer); - struct pixmap pixmap = parse_xbm_tokens(tokens); - *texture = texture_from_pixmap(renderer, &pixmap); - if (tokens) { - free(tokens); - } - if (pixmap.data) { - free(pixmap.data); - } -out: - if (!(*texture)) { - *texture = texture_from_builtin(renderer, button); - } + /* Create buffer with free_on_destroy being true */ + *buffer = buffer_create_wrap(pixmap.data, pixmap.width, pixmap.height, + pixmap.width * 4, true); } void -xbm_load(struct theme *theme, struct wlr_renderer *r) +xbm_load(struct theme *theme) { parse_set_color(theme->window_active_button_menu_unpressed_image_color); - load_button(r, "menu.xbm", &theme->xbm_menu_active_unpressed, + load_button("menu.xbm", &theme->xbm_menu_active_unpressed, menu_button_normal); parse_set_color(theme->window_active_button_iconify_unpressed_image_color); - load_button(r, "iconify.xbm", &theme->xbm_iconify_active_unpressed, + load_button("iconify.xbm", &theme->xbm_iconify_active_unpressed, iconify_button_normal); parse_set_color(theme->window_active_button_max_unpressed_image_color); - load_button(r, "max.xbm", &theme->xbm_maximize_active_unpressed, + load_button("max.xbm", &theme->xbm_maximize_active_unpressed, max_button_normal); parse_set_color(theme->window_active_button_close_unpressed_image_color); - load_button(r, "close.xbm", &theme->xbm_close_active_unpressed, + load_button("close.xbm", &theme->xbm_close_active_unpressed, close_button_normal); parse_set_color(theme->window_inactive_button_menu_unpressed_image_color); - load_button(r, "menu.xbm", &theme->xbm_menu_inactive_unpressed, + load_button("menu.xbm", &theme->xbm_menu_inactive_unpressed, menu_button_normal); parse_set_color(theme->window_inactive_button_iconify_unpressed_image_color); - load_button(r, "iconify.xbm", &theme->xbm_iconify_inactive_unpressed, + load_button("iconify.xbm", &theme->xbm_iconify_inactive_unpressed, iconify_button_normal); parse_set_color(theme->window_inactive_button_max_unpressed_image_color); - load_button(r, "max.xbm", &theme->xbm_maximize_inactive_unpressed, + load_button("max.xbm", &theme->xbm_maximize_inactive_unpressed, max_button_normal); parse_set_color(theme->window_inactive_button_close_unpressed_image_color); - load_button(r, "close.xbm", &theme->xbm_close_inactive_unpressed, + load_button("close.xbm", &theme->xbm_close_inactive_unpressed, close_button_normal); } diff --git a/src/xdg-popup.c b/src/xdg-popup.c index de5a925e..aea05f4d 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -3,47 +3,54 @@ * Copyright (C) 2020 the sway authors * * This file is only needed in support of - * - tracking damage * - unconstraining XDG popups + * - keeping non-layer-shell xdg-popups outside the layers.c code */ #include "labwc.h" +#include "node.h" + +struct view_child { + struct wlr_surface *surface; + struct view *parent; +}; + +struct xdg_popup { + struct view_child view_child; + struct wlr_xdg_popup *wlr_popup; + + struct wl_listener destroy; + struct wl_listener new_popup; +}; static void -xdg_popup_destroy(struct view_child *view_child) +popup_unconstrain(struct view *view, struct wlr_xdg_popup *popup) { - if (!view_child) { - return; - } - struct xdg_popup *popup = (struct xdg_popup *)view_child; - wl_list_remove(&popup->destroy.link); - wl_list_remove(&popup->map.link); - wl_list_remove(&popup->unmap.link); - wl_list_remove(&popup->new_popup.link); - view_child_finish(&popup->view_child); - free(popup); -} + struct server *server = view->server; + struct wlr_box *popup_box = &popup->geometry; + struct wlr_output_layout *output_layout = server->output_layout; + struct wlr_output *wlr_output = wlr_output_layout_output_at( + output_layout, view->x + popup_box->x, view->y + popup_box->y); -static void -handle_xdg_popup_map(struct wl_listener *listener, void *data) -{ - struct xdg_popup *popup = wl_container_of(listener, popup, map); - damage_all_outputs(popup->view_child.parent->server); -} + struct wlr_box output_box; + wlr_output_layout_get_box(output_layout, wlr_output, &output_box); -static void -handle_xdg_popup_unmap(struct wl_listener *listener, void *data) -{ - struct xdg_popup *popup = wl_container_of(listener, popup, unmap); - damage_all_outputs(popup->view_child.parent->server); + struct wlr_box output_toplevel_box = { + .x = output_box.x - view->x, + .y = output_box.y - view->y, + .width = output_box.width, + .height = output_box.height, + }; + wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box); } static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, destroy); - struct view_child *view_child = (struct view_child *)popup; - xdg_popup_destroy(view_child); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->new_popup.link); + free(popup); } static void @@ -54,28 +61,6 @@ popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) xdg_popup_create(popup->view_child.parent, wlr_popup); } -static void -popup_unconstrain(struct xdg_popup *popup) -{ - struct view *view = popup->view_child.parent; - struct server *server = view->server; - struct wlr_box *popup_box = &popup->wlr_popup->geometry; - struct wlr_output_layout *output_layout = server->output_layout; - struct wlr_output *wlr_output = wlr_output_layout_output_at( - output_layout, view->x + popup_box->x, view->y + popup_box->y); - struct wlr_box *output_box = wlr_output_layout_get_box( - output_layout, wlr_output); - - struct wlr_box output_toplevel_box = { - .x = output_box->x - view->x, - .y = output_box->y - view->y, - .width = output_box->width, - .height = output_box->height, - }; - wlr_xdg_popup_unconstrain_from_box( - popup->wlr_popup, &output_toplevel_box); -} - void xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) { @@ -85,16 +70,32 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) } popup->wlr_popup = wlr_popup; - view_child_init(&popup->view_child, view, wlr_popup->base->surface); + popup->view_child.parent = view; + popup->view_child.surface = wlr_popup->base->surface; popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); - popup->map.notify = handle_xdg_popup_map; - wl_signal_add(&wlr_popup->base->events.map, &popup->map); - popup->unmap.notify = handle_xdg_popup_unmap; - wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->new_popup.notify = popup_handle_new_xdg_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); - popup_unconstrain(popup); + /* + * We must add xdg popups to the scene graph so they get rendered. The + * wlroots scene graph provides a helper for this, but to use it we must + * provide the proper parent scene node of the xdg popup. To enable + * this, we always set the user data field of xdg_surfaces to the + * corresponding scene node. + */ + if (!wlr_surface_is_xdg_surface(wlr_popup->parent)) { + wlr_log(WLR_ERROR, "xdg_surface is not xdg"); + return; + } + struct wlr_xdg_surface *parent = + wlr_xdg_surface_from_wlr_surface(wlr_popup->parent); + struct wlr_scene_node *parent_node = parent->surface->data; + wlr_popup->base->surface->data = + wlr_scene_xdg_surface_create(parent_node, wlr_popup->base); + node_descriptor_create(wlr_popup->base->surface->data, + LAB_NODE_DESC_XDG_POPUP, view); + + popup_unconstrain(view, wlr_popup); } diff --git a/src/xdg.c b/src/xdg.c index 02b202e3..dc534499 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -1,12 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only #include #include "labwc.h" +#include "node.h" #include "ssd.h" -/* - * xdg_popup_create() and view_subsurface_create() are only called for the - * purposes of tracking damage. - */ static void handle_new_xdg_popup(struct wl_listener *listener, void *data) { @@ -15,14 +12,6 @@ handle_new_xdg_popup(struct wl_listener *listener, void *data) xdg_popup_create(view, wlr_popup); } -static void -new_subsurface_notify(struct wl_listener *listener, void *data) -{ - struct view *view = wl_container_of(listener, view, new_subsurface); - struct wlr_subsurface *wlr_subsurface = data; - view_subsurface_create(view, wlr_subsurface); -} - static bool has_ssd(struct view *view) { @@ -70,8 +59,8 @@ handle_commit(struct wl_listener *listener, void *data) view->pending_move_resize.configure_serial = 0; } } - ssd_update_geometry(view, false); - damage_view_part(view); + wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); + ssd_update_geometry(view); } static void @@ -98,7 +87,11 @@ handle_destroy(struct wl_listener *listener, void *data) interactive_end(view); wl_list_remove(&view->link); wl_list_remove(&view->destroy.link); - ssd_destroy(view); + if (view->scene_tree) { + ssd_destroy(view); + wlr_scene_node_destroy(&view->scene_tree->node); + view->scene_tree = NULL; + } free(view); } @@ -139,29 +132,23 @@ static void handle_request_minimize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_minimize); - struct wlr_xdg_surface *surface = data; - if (view) { - view_minimize(view, surface->toplevel->requested.minimized); - } + view_minimize(view, view->xdg_surface->toplevel->requested.minimized); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_maximize); - struct wlr_xdg_surface *surface = data; - if (view) { - view_maximize(view, surface->toplevel->requested.maximized); - } - + view_maximize(view, view->xdg_surface->toplevel->requested.maximized); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_fullscreen); - struct wlr_xdg_toplevel_set_fullscreen_event *e = data; - view_set_fullscreen(view, e->fullscreen, e->output); + view_set_fullscreen(view, + view->xdg_surface->toplevel->requested.fullscreen, + view->xdg_surface->toplevel->requested.fullscreen_output); } static void @@ -194,15 +181,16 @@ xdg_toplevel_view_configure(struct view *view, struct wlr_box geo) view->pending_move_resize.width = MAX(geo.width, min_width); view->pending_move_resize.height = MAX(geo.height, min_height); - uint32_t serial = wlr_xdg_toplevel_set_size(view->xdg_surface, + uint32_t serial = wlr_xdg_toplevel_set_size(view->xdg_surface->toplevel, (uint32_t)geo.width, (uint32_t)geo.height); if (serial > 0) { view->pending_move_resize.configure_serial = serial; } else if (view->pending_move_resize.configure_serial == 0) { view->x = geo.x; view->y = geo.y; - ssd_update_geometry(view, false); - damage_all_outputs(view->server); + wlr_scene_node_set_position(&view->scene_tree->node, + view->x, view->y); + ssd_update_geometry(view); } } #undef MAX @@ -212,28 +200,12 @@ xdg_toplevel_view_move(struct view *view, double x, double y) { view->x = x; view->y = y; - ssd_update_geometry(view, false); - damage_all_outputs(view->server); } static void xdg_toplevel_view_close(struct view *view) { - wlr_xdg_toplevel_send_close(view->xdg_surface); -} - -static void -xdg_toplevel_view_for_each_popup_surface(struct view *view, - wlr_surface_iterator_func_t iterator, void *data) -{ - wlr_xdg_surface_for_each_popup_surface(view->xdg_surface, iterator, data); -} - -static void -xdg_toplevel_view_for_each_surface(struct view *view, - wlr_surface_iterator_func_t iterator, void *data) -{ - wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, data); + wlr_xdg_toplevel_send_close(view->xdg_surface->toplevel); } static void @@ -248,7 +220,7 @@ update_padding(struct view *view) static void xdg_toplevel_view_maximize(struct view *view, bool maximized) { - wlr_xdg_toplevel_set_maximized(view->xdg_surface, maximized); + wlr_xdg_toplevel_set_maximized(view->xdg_surface->toplevel, maximized); } static void @@ -256,14 +228,14 @@ xdg_toplevel_view_set_activated(struct view *view, bool activated) { struct wlr_xdg_surface *surface = view->xdg_surface; if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - wlr_xdg_toplevel_set_activated(surface, activated); + wlr_xdg_toplevel_set_activated(surface->toplevel, activated); } } static void xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen) { - wlr_xdg_toplevel_set_fullscreen(view->xdg_surface, fullscreen); + wlr_xdg_toplevel_set_fullscreen(view->xdg_surface->toplevel, fullscreen); } static bool @@ -277,7 +249,8 @@ parent_of(struct view *view) { struct view *p; wl_list_for_each (p, &view->server->views, link) { - if (p->xdg_surface == view->xdg_surface->toplevel->parent) { + if (p->xdg_surface->toplevel + == view->xdg_surface->toplevel->parent) { return p; } } @@ -328,8 +301,12 @@ xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) static void xdg_toplevel_view_map(struct view *view) { + if (view->mapped) { + return; + } view->mapped = true; view->surface = view->xdg_surface->surface; + wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (!view->been_mapped) { struct wlr_xdg_toplevel_requested *requested = &view->xdg_surface->toplevel->requested; @@ -352,16 +329,6 @@ xdg_toplevel_view_map(struct view *view) if (!view->maximized && !view->fullscreen) { position_xdg_toplevel_view(view); } - - struct wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &view->surface->current.subsurfaces_below, - current.link) { - view_subsurface_create(view, subsurface); - } - wl_list_for_each(subsurface, &view->surface->current.subsurfaces_above, - current.link) { - view_subsurface_create(view, subsurface); - } view_discover_output(view); view->been_mapped = true; @@ -370,9 +337,6 @@ xdg_toplevel_view_map(struct view *view) view->commit.notify = handle_commit; wl_signal_add(&view->xdg_surface->surface->events.commit, &view->commit); - view->new_subsurface.notify = new_subsurface_notify; - wl_signal_add(&view->surface->events.new_subsurface, - &view->new_subsurface); view_impl_map(view); } @@ -382,9 +346,8 @@ xdg_toplevel_view_unmap(struct view *view) { if (view->mapped) { view->mapped = false; - damage_all_outputs(view->server); + wlr_scene_node_set_enabled(&view->scene_tree->node, false); wl_list_remove(&view->commit.link); - wl_list_remove(&view->new_subsurface.link); desktop_focus_topmost_mapped_view(view->server); } } @@ -392,8 +355,6 @@ xdg_toplevel_view_unmap(struct view *view) static const struct view_impl xdg_toplevel_view_impl = { .configure = xdg_toplevel_view_configure, .close = xdg_toplevel_view_close, - .for_each_popup_surface = xdg_toplevel_view_for_each_popup_surface, - .for_each_surface = xdg_toplevel_view_for_each_surface, .get_string_prop = xdg_toplevel_view_get_string_prop, .map = xdg_toplevel_view_map, .move = xdg_toplevel_view_move, @@ -403,15 +364,28 @@ static const struct view_impl xdg_toplevel_view_impl = { .maximize = xdg_toplevel_view_maximize, }; +/* + * We use the following struct user_data pointers: + * - wlr_xdg_surface->data = view + * for the wlr_xdg_toplevel_decoration_v1 implementation + * - wlr_surface->data = scene_node + * to help the popups find their parent nodes + */ void xdg_surface_new(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, new_xdg_surface); struct wlr_xdg_surface *xdg_surface = data; + + /* + * We deal with popups in xdg-popup.c and layers.c as they have to be + * treated differently + */ if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } + wlr_xdg_surface_ping(xdg_surface); struct view *view = calloc(1, sizeof(struct view)); @@ -419,10 +393,26 @@ xdg_surface_new(struct wl_listener *listener, void *data) view->type = LAB_XDG_SHELL_VIEW; view->impl = &xdg_toplevel_view_impl; view->xdg_surface = xdg_surface; - wl_list_init(&view->ssd.parts); + view->scene_tree = wlr_scene_tree_create(&view->server->view_tree->node); + wlr_scene_node_set_enabled(&view->scene_tree->node, false); + + view->scene_node = wlr_scene_xdg_surface_create( + &view->scene_tree->node, view->xdg_surface); + if (!view->scene_node) { + /* TODO: might need further clean up */ + wl_resource_post_no_memory(view->surface->resource); + return; + } + node_descriptor_create(&view->scene_tree->node, + LAB_NODE_DESC_VIEW, view); + + /* In support of xdg_toplevel_decoration */ xdg_surface->data = view; + /* In support of xdg popups */ + xdg_surface->surface->data = view->scene_node; + view->map.notify = handle_map; wl_signal_add(&xdg_surface->events.map, &view->map); view->unmap.notify = handle_unmap; diff --git a/src/xwayland-unmanaged.c b/src/xwayland-unmanaged.c index b5c5922a..3c37dfd2 100644 --- a/src/xwayland-unmanaged.c +++ b/src/xwayland-unmanaged.c @@ -20,7 +20,22 @@ unmanaged_handle_commit(struct wl_listener *listener, void *data) struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; unmanaged->lx = xsurface->x; unmanaged->ly = xsurface->y; - damage_all_outputs(unmanaged->server); +} + +static struct view * +parent_view(struct server *server, struct wlr_xwayland_surface *surface) +{ + struct wlr_xwayland_surface *s = surface; + while (s->parent) { + s = s->parent; + } + struct view *view; + wl_list_for_each(view, &server->views, link) { + if (view->xwayland_surface == s) { + return view; + } + } + return NULL; } static void @@ -38,10 +53,24 @@ unmanaged_handle_map(struct wl_listener *listener, void *data) unmanaged->lx = xsurface->x; unmanaged->ly = xsurface->y; - damage_all_outputs(unmanaged->server); if (wlr_xwayland_or_surface_wants_focus(xsurface)) { seat_focus_surface(&unmanaged->server->seat, xsurface->surface); } + + int lx = unmanaged->lx; + int ly = unmanaged->ly; + struct wlr_scene_node *parent, *node; + struct view *view = parent_view(unmanaged->server, xsurface); + if (!view || !view->scene_node) { + parent = &unmanaged->server->unmanaged_tree->node; + } else { + lx -= view->x; + ly -= view->y; + parent = &view->scene_tree->node; + } + /* node will be destroyed automatically once surface is destroyed */ + node = &wlr_scene_surface_create(parent, xsurface->surface)->node; + wlr_scene_node_set_position(node, lx, ly); } static void @@ -50,7 +79,6 @@ unmanaged_handle_unmap(struct wl_listener *listener, void *data) struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, unmap); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; - damage_all_outputs(unmanaged->server); wl_list_remove(&unmanaged->link); wl_list_remove(&unmanaged->commit.link); diff --git a/src/xwayland.c b/src/xwayland.c index a8194562..7374a819 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include #include "labwc.h" +#include "node.h" #include "ssd.h" static void @@ -10,21 +11,37 @@ handle_commit(struct wl_listener *listener, void *data) assert(view->surface); /* Must receive commit signal before accessing surface->current* */ - view->w = view->surface->current.width; - view->h = view->surface->current.height; + struct wlr_surface_state *state = &view->surface->current; + struct view_pending_move_resize *pending = &view->pending_move_resize; - if (view->pending_move_resize.update_x) { - view->x = view->pending_move_resize.x + - view->pending_move_resize.width - view->w; - view->pending_move_resize.update_x = false; + bool move_pending = pending->update_x || pending->update_y; + bool size_changed = view->w != state->width || view->h != state->height; + + if (!move_pending && !size_changed) { + return; } - if (view->pending_move_resize.update_y) { - view->y = view->pending_move_resize.y + - view->pending_move_resize.height - view->h; - view->pending_move_resize.update_y = false; + + view->w = state->width; + view->h = state->height; + + if (pending->update_x) { + /* Adjust x for queued up configure events */ + view->x = pending->x + pending->width - view->w; } - ssd_update_geometry(view, false); - damage_view_whole(view); + if (pending->update_y) { + /* Adjust y for queued up configure events */ + view->y = pending->y + pending->height - view->h; + } + if (move_pending) { + wlr_scene_node_set_position(&view->scene_tree->node, + view->x, view->y); + } + if ((int)pending->width == view->w && (int)pending->height == view->h) { + /* We reached the end of all queued size changing configure events */ + pending->update_x = false; + pending->update_y = false; + } + ssd_update_geometry(view); } static void @@ -90,7 +107,11 @@ handle_destroy(struct wl_listener *listener, void *data) wl_list_remove(&view->request_configure.link); wl_list_remove(&view->request_maximize.link); wl_list_remove(&view->request_fullscreen.link); - ssd_destroy(view); + if (view->scene_tree) { + ssd_destroy(view); + wlr_scene_node_destroy(&view->scene_tree->node); + view->scene_tree = NULL; + } free(view); } @@ -107,7 +128,6 @@ handle_request_configure(struct wl_listener *listener, void *data) wlr_xwayland_surface_configure(view->xwayland_surface, event->x, event->y, MAX(event->width, min_width), MAX(event->height, min_height)); - damage_all_outputs(view->server); } #undef MAX @@ -173,7 +193,6 @@ configure(struct view *view, struct wlr_box geo) wlr_xwayland_surface_configure(view->xwayland_surface, (int16_t)geo.x, (int16_t)geo.y, (uint16_t)geo.width, (uint16_t)geo.height); - damage_all_outputs(view->server); } static void @@ -184,25 +203,12 @@ move(struct view *view, double x, double y) struct wlr_xwayland_surface *s = view->xwayland_surface; wlr_xwayland_surface_configure(s, (int16_t)x, (int16_t)y, (uint16_t)s->width, (uint16_t)s->height); - ssd_update_geometry(view, false); - damage_all_outputs(view->server); } static void _close(struct view *view) { wlr_xwayland_surface_close(view->xwayland_surface); - damage_all_outputs(view->server); -} - -static void -for_each_surface(struct view *view, wlr_surface_iterator_func_t iterator, - void *data) -{ - if (!view->surface) { - return; - } - wlr_surface_for_each_surface(view->surface, iterator, data); } static const char * @@ -254,7 +260,11 @@ top_left_edge_boundary_check(struct view *view) static void map(struct view *view) { + if (view->mapped) { + return; + } view->mapped = true; + wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (!view->fullscreen && view->xwayland_surface->fullscreen) { view_set_fullscreen(view, true, NULL); } @@ -264,7 +274,18 @@ map(struct view *view) view->w = view->xwayland_surface->width; view->h = view->xwayland_surface->height; } - view->surface = view->xwayland_surface->surface; + + if (view->surface != view->xwayland_surface->surface) { + view->surface = view->xwayland_surface->surface; + view->scene_node = wlr_scene_subsurface_tree_create( + &view->scene_tree->node, view->surface); + if (!view->scene_node) { + /* TODO: might need further clean up */ + wl_resource_post_no_memory(view->surface->resource); + return; + } + } + view->ssd.enabled = want_deco(view); if (view->ssd.enabled) { @@ -304,7 +325,7 @@ unmap(struct view *view) { if (view->mapped) { view->mapped = false; - damage_all_outputs(view->server); + wlr_scene_node_set_enabled(&view->scene_tree->node, false); wl_list_remove(&view->commit.link); desktop_focus_topmost_mapped_view(view->server); } @@ -338,7 +359,6 @@ set_fullscreen(struct view *view, bool fullscreen) static const struct view_impl xwl_view_impl = { .configure = configure, .close = _close, - .for_each_surface = for_each_surface, .get_string_prop = get_string_prop, .map = map, .move = move, @@ -370,8 +390,10 @@ xwayland_surface_new(struct wl_listener *listener, void *data) view->type = LAB_XWAYLAND_VIEW; view->impl = &xwl_view_impl; view->xwayland_surface = xsurface; - wl_list_init(&view->ssd.parts); + view->scene_tree = wlr_scene_tree_create(&view->server->view_tree->node); + node_descriptor_create(&view->scene_tree->node, + LAB_NODE_DESC_VIEW, view); xsurface->data = view; view->map.notify = handle_map; diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 314d2c36..3c65aaac 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,3 +1,3 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 0.15.1 +revision = master