summaryrefslogtreecommitdiff
path: root/gui-libs/pixdecor/files/pixdecor-shade-output-lifetime.patch
blob: 0ae4bd78f0b0d0680207777c33dd57a04e1b37ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
--- a/src/shade.hpp
+++ b/src/shade.hpp
@@ -4,6 +4,7 @@
 #include <cmath>
 
 #include <wayfire/output.hpp>
+#include <wayfire/output-layout.hpp>
 #include <wayfire/opengl.hpp>
 #include <wayfire/core.hpp>
 #include <wayfire/view-transform.hpp>
@@ -34,7 +35,11 @@
 {
     nonstd::observer_ptr<simple_decorator_t> deco = nullptr;
     wayfire_view view;
-    wf::output_t *output;
+    /* The output that currently holds pre_hook. Re-resolved from the view and
+     * migrated when the view changes output, and detached (while the output is
+     * still alive) when that output is removed, so a cached pointer can never
+     * dangle into a rem_effect() use-after-free. */
+    wf::output_t *hook_output = nullptr;
     int titlebar_height;
     wf::option_wrapper_t<wf::animation_description_t> shade_duration{"pixdecor/shade_duration"};
 
@@ -136,13 +141,10 @@
     pixdecor_shade(wayfire_view view, int titlebar_height) : wf::scene::view_2d_transformer_t(view)
     {
         this->view   = view;
-        this->output = view->get_output();
         this->titlebar_height = titlebar_height;
         this->progression.shade.set(0.0, 1.0);
-        if (output)
-        {
-            output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE);
-        }
+        set_hook_output(view->get_output());
+        wf::get_core().output_layout->connect(&on_output_removed);
 
         if (auto toplevel = wf::toplevel_cast(view))
         {
@@ -163,8 +165,55 @@
         }
     }
 
+    /* Move pre_hook onto output o (which may be null), removing it from whatever
+     * output currently holds it. All add/rem of pre_hook goes through here so the
+     * tracked output stays in sync and is only ever a live pointer. */
+    void set_hook_output(wf::output_t *o)
+    {
+        if (hook_output == o)
+        {
+            return;
+        }
+
+        if (hook_output)
+        {
+            hook_output->render->rem_effect(&pre_hook);
+        }
+
+        hook_output = o;
+        if (hook_output)
+        {
+            hook_output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE);
+        }
+    }
+
+    wf::signal::connection_t<wf::output_removed_signal> on_output_removed =
+        [=] (wf::output_removed_signal *ev)
+    {
+        if (ev->output != hook_output)
+        {
+            return;
+        }
+
+        /* Detach while the output is still alive, then re-home onto the view's
+         * current output if it has already migrated, so the animation can finish
+         * instead of leaving a stale hook (or dangling pointer) behind. */
+        hook_output->render->rem_effect(&pre_hook);
+        hook_output = nullptr;
+        if (auto o = view ? view->get_output() : nullptr; o && (o != ev->output))
+        {
+            set_hook_output(o);
+        }
+    };
+
     wf::effect_hook_t pre_hook = [=] ()
     {
+        /* Follow the view if it has moved to another output since last frame. */
+        if (view && view->get_output() && (view->get_output() != hook_output))
+        {
+            set_hook_output(view->get_output());
+        }
+
         if (auto toplevel = wf::toplevel_cast(view))
         {
             /* Re-resolve the decorator each frame: a cached observer_ptr dangles
@@ -202,11 +251,7 @@
         view->damage();
         if (!this->progression.running() && !last_direction)
         {
-            if (output)
-            {
-                output->render->rem_effect(&pre_hook);
-            }
-
+            set_hook_output(nullptr);
             pop_transformer(view);
         }
     };
@@ -282,10 +327,9 @@
 
     virtual ~pixdecor_shade()
     {
-        if (output)
-        {
-            output->render->rem_effect(&pre_hook);
-        }
+        /* hook_output is kept valid (nulled when its output is removed), so this
+         * detaches cleanly instead of dereferencing a freed output. */
+        set_hook_output(nullptr);
     }
 };
 }