Unpublish scene trees from surfaces outliving them
bd14fad1446df5403ab00341e646dd7ce8cd821d
1 parent
a09d7a06
swm.c
+58 -21
| 102 | 102 | #define MAX_LAYER_SURFACES 256 |
|
| 103 | 103 | #define MAX_KEYBOARD_GROUPS 32 |
|
| 104 | 104 | #define MAX_POINTER_CONSTRAINTS 256 |
|
| 105 | 105 | #define MAX_TEXT_INPUTS 128 |
|
| 106 | 106 | #define MAX_INPUT_POPUPS 64 |
|
| 107 | + | #define MAX_POPUPS 256 |
|
| 107 | 108 | #define MAX_SESSION_LOCKS 4 |
|
| 108 | 109 | #define MAX_PENDING_SPAWNS 256 |
|
| 109 | 110 | #define MAX_WINDOW_STATES 512 |
|
| 110 | 111 | #define MAX_WINDOW_STATE_FIELD 256 |
|
| 111 | 112 | #define MAX_WINDOW_STATE_LINE 1024 |
| 376 | 377 | struct wl_listener commit; |
|
| 377 | 378 | struct wl_listener disable; |
|
| 378 | 379 | struct wl_listener destroy; |
|
| 379 | 380 | } text_input_t; |
|
| 380 | 381 | ||
| 382 | + | /* XDG popup state. */ |
|
| 383 | + | typedef struct { |
|
| 384 | + | struct wlr_xdg_popup *popup; |
|
| 385 | + | struct wl_listener commit; |
|
| 386 | + | struct wl_listener destroy; |
|
| 387 | + | } popup_t; |
|
| 388 | + | ||
| 381 | 389 | /* Input-method popup state. */ |
|
| 382 | 390 | typedef struct { |
|
| 383 | 391 | struct wlr_input_popup_surface_v2 *popup; |
|
| 384 | 392 | struct wlr_scene_tree *scene; |
|
| 385 | 393 | struct wl_list link; |
| 450 | 458 | static void cleanup_listeners(void); |
|
| 451 | 459 | static void close_monitor(monitor_t *m); |
|
| 452 | 460 | static void layer_surface_commit_notify(struct wl_listener *listener, void *data); |
|
| 453 | 461 | static void commit_notify(struct wl_listener *listener, void *data); |
|
| 454 | 462 | static void popup_commit(struct wl_listener *listener, void *data); |
|
| 463 | + | static void popup_destroy(struct wl_listener *listener, void *data); |
|
| 455 | 464 | static void create_decoration(struct wl_listener *listener, void *data); |
|
| 456 | 465 | static void create_idle_inhibitor(struct wl_listener *listener, void *data); |
|
| 457 | 466 | static void create_keyboard(struct wlr_keyboard *keyboard); |
|
| 458 | 467 | static keyboard_group_t *create_keyboard_group(bool is_virtual); |
|
| 459 | 468 | static void create_layer_surface(struct wl_listener *listener, void *data); |
| 825 | 834 | static layer_surface_t layer_surface_items[MAX_LAYER_SURFACES]; |
|
| 826 | 835 | static keyboard_group_t keyboard_group_items[MAX_KEYBOARD_GROUPS]; |
|
| 827 | 836 | static pointer_constraint_t pointer_constraint_items[MAX_POINTER_CONSTRAINTS]; |
|
| 828 | 837 | static text_input_t text_input_items[MAX_TEXT_INPUTS]; |
|
| 829 | 838 | static input_popup_t input_popup_items[MAX_INPUT_POPUPS]; |
|
| 839 | + | static popup_t popup_items[MAX_POPUPS]; |
|
| 830 | 840 | static session_lock_t session_lock_items[MAX_SESSION_LOCKS]; |
|
| 831 | 841 | static pending_spawn_t pending_spawn_items[MAX_PENDING_SPAWNS]; |
|
| 832 | 842 | static window_state_t window_state_items[MAX_WINDOW_STATES]; |
|
| 833 | 843 | static static_listener_t static_listener_items[MAX_STATIC_LISTENERS]; |
|
| 834 | 844 | static workspace_manager_t workspace_manager_items[MAX_WS_MANAGERS]; |
| 837 | 847 | ||
| 838 | 848 | static bool client_used[MAX_CLIENTS], monitor_used[MAX_MONITORS]; |
|
| 839 | 849 | static bool layer_surface_used[MAX_LAYER_SURFACES], keyboard_group_used[MAX_KEYBOARD_GROUPS]; |
|
| 840 | 850 | static bool pointer_constraint_used[MAX_POINTER_CONSTRAINTS], text_input_used[MAX_TEXT_INPUTS]; |
|
| 841 | 851 | static bool input_popup_used[MAX_INPUT_POPUPS], session_lock_used[MAX_SESSION_LOCKS]; |
|
| 852 | + | static bool popup_used[MAX_POPUPS]; |
|
| 842 | 853 | static bool pending_spawn_used[MAX_PENDING_SPAWNS], window_state_used[MAX_WINDOW_STATES]; |
|
| 843 | 854 | static bool static_listener_used[MAX_STATIC_LISTENERS], workspace_manager_used[MAX_WS_MANAGERS]; |
|
| 844 | 855 | static bool workspace_handle_used[MAX_WS_HANDLES], metadata_manager_used[MAX_WS_MANAGERS]; |
|
| 845 | 856 | ||
| 846 | 857 | static pool_t client_pool = { |
| 907 | 918 | static pool_t metadata_manager_pool = { metadata_manager_items, |
|
| 908 | 919 | metadata_manager_used, |
|
| 909 | 920 | LENGTH(metadata_manager_items), |
|
| 910 | 921 | sizeof *metadata_manager_items, |
|
| 911 | 922 | "metadata_manager" }; |
|
| 923 | + | static pool_t popup_pool = { |
|
| 924 | + | popup_items, popup_used, LENGTH(popup_items), sizeof *popup_items, "popup" |
|
| 925 | + | }; |
|
| 912 | 926 | ||
| 913 | 927 | /* Backend-specific client operations. */ |
|
| 914 | 928 | /* Client helpers shared by the XDG shell and Xwayland implementations. These |
|
| 915 | 929 | * are inline so unused helpers are omitted for builds that disable a backend. */ |
|
| 916 | 930 |
| 2321 | 2335 | c->resize_edges = WLR_EDGE_NONE; |
|
| 2322 | 2336 | } |
|
| 2323 | 2337 | ||
| 2324 | 2338 | /* Create and constrain a popup after its initial state is committed. */ |
|
| 2325 | 2339 | void popup_commit(struct wl_listener *listener, void *data) { |
|
| 2326 | - | struct wlr_surface *surface = data; |
|
| 2327 | - | struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); |
|
| 2328 | - | layer_surface_t *l = nullptr; |
|
| 2329 | - | client_t *c = nullptr; |
|
| 2340 | + | popup_t *p = wl_container_of(listener, p, commit); |
|
| 2341 | + | struct wlr_xdg_popup *popup = p->popup; |
|
| 2342 | + | layer_surface_t *l = nullptr; |
|
| 2343 | + | client_t *c = nullptr; |
|
| 2330 | 2344 | struct wlr_box box; |
|
| 2331 | 2345 | int type = -1; |
|
| 2332 | 2346 | ||
| 2333 | - | if (!popup) { |
|
| 2334 | - | wl_list_remove(&listener->link); |
|
| 2335 | - | listener_release(listener); |
|
| 2336 | - | return; |
|
| 2337 | - | } |
|
| 2338 | 2347 | if (!popup->base->initial_commit) |
|
| 2339 | 2348 | return; |
|
| 2340 | 2349 | ||
| 2341 | 2350 | type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); |
|
| 2342 | 2351 | ||
| 2343 | 2352 | if ((type == LAYER_SHELL && (!l || !l->mon)) || (type != LAYER_SHELL && (!c || !c->mon)) || |
|
| 2344 | - | !popup->parent || !popup->parent->data) |
|
| 2345 | - | goto destroy_popup; |
|
| 2353 | + | !popup->parent || !popup->parent->data) { |
|
| 2354 | + | wlr_xdg_popup_destroy(popup); |
|
| 2355 | + | return; |
|
| 2356 | + | } |
|
| 2346 | 2357 | popup->base->surface->data = wlr_scene_xdg_surface_create(popup->parent->data, popup->base); |
|
| 2347 | 2358 | ||
| 2348 | - | if (!popup->base->surface->data) |
|
| 2349 | - | goto destroy_popup; |
|
| 2359 | + | if (!popup->base->surface->data) { |
|
| 2360 | + | wlr_xdg_popup_destroy(popup); |
|
| 2361 | + | return; |
|
| 2362 | + | } |
|
| 2350 | 2363 | box = type == LAYER_SHELL ? l->mon->m : c->mon->w; |
|
| 2351 | 2364 | box.x -= (type == LAYER_SHELL ? l->scene->node.x : c->geom.x); |
|
| 2352 | 2365 | box.y -= (type == LAYER_SHELL ? l->scene->node.y : c->geom.y); |
|
| 2353 | 2366 | wlr_xdg_popup_unconstrain_from_box(popup, &box); |
|
| 2354 | - | wl_list_remove(&listener->link); |
|
| 2355 | - | listener_release(listener); |
|
| 2356 | - | return; |
|
| 2367 | + | } |
|
| 2357 | 2368 | ||
| 2358 | - | destroy_popup: |
|
| 2359 | - | wl_list_remove(&listener->link); |
|
| 2360 | - | listener_release(listener); |
|
| 2361 | - | wlr_xdg_popup_destroy(popup); |
|
| 2369 | + | /* Unpublish a destroyed popup's scene tree and release its state. */ |
|
| 2370 | + | void popup_destroy(struct wl_listener *listener, void *data) { |
|
| 2371 | + | popup_t *p = wl_container_of(listener, p, destroy); |
|
| 2372 | + | ||
| 2373 | + | /* The scene tree dies with the popup, but its wl_surface can outlive both. */ |
|
| 2374 | + | p->popup->base->surface->data = nullptr; |
|
| 2375 | + | wl_list_remove(&p->commit.link); |
|
| 2376 | + | wl_list_remove(&p->destroy.link); |
|
| 2377 | + | pool_release(&popup_pool, p); |
|
| 2362 | 2378 | } |
|
| 2363 | 2379 | ||
| 2364 | 2380 | /* Attach an event listener from the fixed-capacity listener pool. */ |
|
| 2365 | 2381 | void listen_static(struct wl_signal *signal, wl_notify_func_t notify) { |
|
| 2366 | 2382 | static_listener_t *slot = pool_take(&static_listener_pool); |
| 2758 | 2774 | /* Wait for a new popup's initial commit before adding it to the scene. */ |
|
| 2759 | 2775 | void create_popup(struct wl_listener *listener, void *data) { |
|
| 2760 | 2776 | /* This event is raised when a client (either xdg-shell or layer-shell) |
|
| 2761 | 2777 | * creates a new popup. */ |
|
| 2762 | 2778 | struct wlr_xdg_popup *popup = data; |
|
| 2779 | + | popup_t *p = pool_take(&popup_pool); |
|
| 2780 | + | ||
| 2781 | + | if (!p) |
|
| 2782 | + | return; |
|
| 2783 | + | p->popup = popup; |
|
| 2763 | 2784 | ||
| 2764 | - | LISTEN_STATIC(&popup->base->surface->events.commit, popup_commit); |
|
| 2785 | + | LISTEN(&popup->base->surface->events.commit, &p->commit, popup_commit); |
|
| 2786 | + | LISTEN(&popup->events.destroy, &p->destroy, popup_destroy); |
|
| 2765 | 2787 | } |
|
| 2766 | 2788 | ||
| 2767 | 2789 | /* Activate the pointer constraint belonging to the focused surface. */ |
|
| 2768 | 2790 | void cursor_constrain(struct wlr_pointer_constraint_v1 *constraint) { |
|
| 2769 | 2791 | if (active_constraint == constraint) |
| 2840 | 2862 | ||
| 2841 | 2863 | wl_list_remove(&l->link); |
|
| 2842 | 2864 | wl_list_remove(&l->destroy.link); |
|
| 2843 | 2865 | wl_list_remove(&l->unmap.link); |
|
| 2844 | 2866 | wl_list_remove(&l->surface_commit.link); |
|
| 2867 | + | /* A wl_surface outlives the role object built on it. Drop the scene tree |
|
| 2868 | + | * published in its user data before destroying that tree. */ |
|
| 2869 | + | l->layer_surface->surface->data = nullptr; |
|
| 2845 | 2870 | wlr_scene_node_destroy(&l->popups->node); |
|
| 2846 | 2871 | ||
| 2847 | 2872 | if (l->dim) |
|
| 2848 | 2873 | wlr_scene_node_destroy(&l->dim->node); |
|
| 2849 | 2874 | pool_release(&layer_surface_pool, l); |
|
| 2850 | 2875 | } |
|
| 2851 | 2876 | ||
| 2852 | 2877 | /* Handle a session locker that exits without unlocking cleanly. */ |
|
| 2853 | 2878 | void destroy_lock(session_lock_t *lock, int unlock) { |
|
| 2879 | + | monitor_t *m; |
|
| 2880 | + | ||
| 2854 | 2881 | wlr_seat_keyboard_notify_clear_focus(seat); |
|
| 2855 | 2882 | input_method_set_focus(nullptr); |
|
| 2856 | 2883 | locked = !unlock; |
|
| 2857 | 2884 | ||
| 2858 | 2885 | if (unlock) { |
| 2862 | 2889 | } |
|
| 2863 | 2890 | wl_list_remove(&lock->new_surface.link); |
|
| 2864 | 2891 | wl_list_remove(&lock->unlock.link); |
|
| 2865 | 2892 | wl_list_remove(&lock->destroy.link); |
|
| 2866 | 2893 | ||
| 2894 | + | /* Detach the lock surfaces still attached to a display, since the scene |
|
| 2895 | + | * destroyed below owns their trees. Keyboard focus was cleared above, so |
|
| 2896 | + | * this only unpublishes those trees and drops their destroy listeners. */ |
|
| 2897 | + | wl_list_for_each(m, &mons, link) { |
|
| 2898 | + | if (m->lock_surface) |
|
| 2899 | + | destroy_lock_surface(&m->destroy_lock_surface, nullptr); |
|
| 2900 | + | } |
|
| 2867 | 2901 | wlr_scene_node_destroy(&lock->scene->node); |
|
| 2868 | 2902 | cur_lock = nullptr; |
|
| 2869 | 2903 | pool_release(&session_lock_pool, lock); |
|
| 2870 | 2904 | } |
|
| 2871 | 2905 |
| 2874 | 2908 | monitor_t *m = wl_container_of(listener, m, destroy_lock_surface); |
|
| 2875 | 2909 | struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; |
|
| 2876 | 2910 | ||
| 2877 | 2911 | m->lock_surface = nullptr; |
|
| 2878 | 2912 | wl_list_remove(&m->destroy_lock_surface.link); |
|
| 2913 | + | /* The wl_surface can outlive the lock surface built on it, while its scene |
|
| 2914 | + | * tree cannot. */ |
|
| 2915 | + | lock_surface->surface->data = nullptr; |
|
| 2879 | 2916 | ||
| 2880 | 2917 | if (lock_surface->surface != seat->keyboard_state.focused_surface) |
|
| 2881 | 2918 | return; |
|
| 2882 | 2919 | ||
| 2883 | 2920 | if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { |