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);
}
};
}
|