diff options
Diffstat (limited to 'sway/lock.c')
-rw-r--r-- | sway/lock.c | 352 |
1 files changed, 261 insertions, 91 deletions
diff --git a/sway/lock.c b/sway/lock.c index 04f80079..289e8ca4 100644 --- a/sway/lock.c +++ b/sway/lock.c | |||
@@ -1,101 +1,229 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <assert.h> | 1 | #include <assert.h> |
2 | #include <wlr/types/wlr_scene.h> | ||
3 | #include <wlr/types/wlr_session_lock_v1.h> | ||
3 | #include "log.h" | 4 | #include "log.h" |
5 | #include "sway/input/cursor.h" | ||
4 | #include "sway/input/keyboard.h" | 6 | #include "sway/input/keyboard.h" |
5 | #include "sway/input/seat.h" | 7 | #include "sway/input/seat.h" |
8 | #include "sway/layers.h" | ||
6 | #include "sway/output.h" | 9 | #include "sway/output.h" |
7 | #include "sway/server.h" | 10 | #include "sway/server.h" |
8 | 11 | ||
9 | struct sway_session_lock_surface { | 12 | struct sway_session_lock_output { |
10 | struct wlr_session_lock_surface_v1 *lock_surface; | 13 | struct wlr_scene_tree *tree; |
14 | struct wlr_scene_rect *background; | ||
15 | struct sway_session_lock *lock; | ||
16 | |||
11 | struct sway_output *output; | 17 | struct sway_output *output; |
12 | struct wlr_surface *surface; | 18 | |
13 | struct wl_listener map; | 19 | struct wl_list link; // sway_session_lock::outputs |
20 | |||
14 | struct wl_listener destroy; | 21 | struct wl_listener destroy; |
15 | struct wl_listener surface_commit; | 22 | struct wl_listener commit; |
16 | struct wl_listener output_mode; | 23 | |
17 | struct wl_listener output_commit; | 24 | struct wlr_session_lock_surface_v1 *surface; |
25 | |||
26 | // invalid if surface is NULL | ||
27 | struct wl_listener surface_destroy; | ||
28 | struct wl_listener surface_map; | ||
18 | }; | 29 | }; |
19 | 30 | ||
31 | static void focus_surface(struct sway_session_lock *lock, | ||
32 | struct wlr_surface *focused) { | ||
33 | lock->focused = focused; | ||
34 | |||
35 | struct sway_seat *seat; | ||
36 | wl_list_for_each(seat, &server.input->seats, link) { | ||
37 | seat_set_focus_surface(seat, focused, false); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | static void refocus_output(struct sway_session_lock_output *output) { | ||
42 | // Move the seat focus to another surface if one is available | ||
43 | if (output->lock->focused == output->surface->surface) { | ||
44 | struct wlr_surface *next_focus = NULL; | ||
45 | |||
46 | struct sway_session_lock_output *candidate; | ||
47 | wl_list_for_each(candidate, &output->lock->outputs, link) { | ||
48 | if (candidate == output || !candidate->surface) { | ||
49 | continue; | ||
50 | } | ||
51 | |||
52 | if (candidate->surface->surface->mapped) { | ||
53 | next_focus = candidate->surface->surface; | ||
54 | break; | ||
55 | } | ||
56 | } | ||
57 | |||
58 | focus_surface(output->lock, next_focus); | ||
59 | } | ||
60 | } | ||
61 | |||
20 | static void handle_surface_map(struct wl_listener *listener, void *data) { | 62 | static void handle_surface_map(struct wl_listener *listener, void *data) { |
21 | struct sway_session_lock_surface *surf = wl_container_of(listener, surf, map); | 63 | struct sway_session_lock_output *surf = wl_container_of(listener, surf, surface_map); |
22 | sway_force_focus(surf->surface); | 64 | if (surf->lock->focused == NULL) { |
23 | output_damage_whole(surf->output); | 65 | focus_surface(surf->lock, surf->surface->surface); |
66 | } | ||
67 | cursor_rebase_all(); | ||
68 | } | ||
69 | |||
70 | static void handle_surface_destroy(struct wl_listener *listener, void *data) { | ||
71 | struct sway_session_lock_output *output = | ||
72 | wl_container_of(listener, output, surface_destroy); | ||
73 | refocus_output(output); | ||
74 | |||
75 | sway_assert(output->surface, "Trying to destroy a surface that the lock doesn't think exists"); | ||
76 | output->surface = NULL; | ||
77 | wl_list_remove(&output->surface_destroy.link); | ||
78 | wl_list_remove(&output->surface_map.link); | ||
79 | } | ||
80 | |||
81 | static void lock_output_reconfigure(struct sway_session_lock_output *output) { | ||
82 | int width = output->output->width; | ||
83 | int height = output->output->height; | ||
84 | |||
85 | wlr_scene_rect_set_size(output->background, width, height); | ||
86 | |||
87 | if (output->surface) { | ||
88 | wlr_session_lock_surface_v1_configure(output->surface, width, height); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | static void handle_new_surface(struct wl_listener *listener, void *data) { | ||
93 | struct sway_session_lock *lock = wl_container_of(listener, lock, new_surface); | ||
94 | struct wlr_session_lock_surface_v1 *lock_surface = data; | ||
95 | struct sway_output *output = lock_surface->output->data; | ||
96 | |||
97 | sway_log(SWAY_DEBUG, "new lock layer surface"); | ||
98 | |||
99 | struct sway_session_lock_output *current_lock_output, *lock_output = NULL; | ||
100 | wl_list_for_each(current_lock_output, &lock->outputs, link) { | ||
101 | if (current_lock_output->output == output) { | ||
102 | lock_output = current_lock_output; | ||
103 | break; | ||
104 | } | ||
105 | } | ||
106 | sway_assert(lock_output, "Couldn't find output to lock"); | ||
107 | sway_assert(!lock_output->surface, "Tried to reassign a surface to an existing output"); | ||
108 | |||
109 | lock_output->surface = lock_surface; | ||
110 | |||
111 | wlr_scene_subsurface_tree_create(lock_output->tree, lock_surface->surface); | ||
112 | |||
113 | lock_output->surface_destroy.notify = handle_surface_destroy; | ||
114 | wl_signal_add(&lock_surface->events.destroy, &lock_output->surface_destroy); | ||
115 | lock_output->surface_map.notify = handle_surface_map; | ||
116 | wl_signal_add(&lock_surface->surface->events.map, &lock_output->surface_map); | ||
117 | |||
118 | lock_output_reconfigure(lock_output); | ||
24 | } | 119 | } |
25 | 120 | ||
26 | static void handle_surface_commit(struct wl_listener *listener, void *data) { | 121 | static void sway_session_lock_output_destroy(struct sway_session_lock_output *output) { |
27 | struct sway_session_lock_surface *surf = wl_container_of(listener, surf, surface_commit); | 122 | if (output->surface) { |
28 | output_damage_surface(surf->output, 0, 0, surf->surface, false); | 123 | refocus_output(output); |
124 | wl_list_remove(&output->surface_destroy.link); | ||
125 | wl_list_remove(&output->surface_map.link); | ||
126 | } | ||
127 | |||
128 | wl_list_remove(&output->commit.link); | ||
129 | wl_list_remove(&output->destroy.link); | ||
130 | wl_list_remove(&output->link); | ||
131 | |||
132 | free(output); | ||
29 | } | 133 | } |
30 | 134 | ||
31 | static void handle_output_mode(struct wl_listener *listener, void *data) { | 135 | static void lock_node_handle_destroy(struct wl_listener *listener, void *data) { |
32 | struct sway_session_lock_surface *surf = wl_container_of(listener, surf, output_mode); | 136 | struct sway_session_lock_output *output = |
33 | wlr_session_lock_surface_v1_configure(surf->lock_surface, | 137 | wl_container_of(listener, output, destroy); |
34 | surf->output->width, surf->output->height); | 138 | sway_session_lock_output_destroy(output); |
35 | } | 139 | } |
36 | 140 | ||
37 | static void handle_output_commit(struct wl_listener *listener, void *data) { | 141 | static void lock_output_handle_commit(struct wl_listener *listener, void *data) { |
38 | struct wlr_output_event_commit *event = data; | 142 | struct wlr_output_event_commit *event = data; |
39 | struct sway_session_lock_surface *surf = wl_container_of(listener, surf, output_commit); | 143 | struct sway_session_lock_output *output = |
40 | if (event->committed & ( | 144 | wl_container_of(listener, output, commit); |
145 | if (event->state->committed & ( | ||
41 | WLR_OUTPUT_STATE_MODE | | 146 | WLR_OUTPUT_STATE_MODE | |
42 | WLR_OUTPUT_STATE_SCALE | | 147 | WLR_OUTPUT_STATE_SCALE | |
43 | WLR_OUTPUT_STATE_TRANSFORM)) { | 148 | WLR_OUTPUT_STATE_TRANSFORM)) { |
44 | wlr_session_lock_surface_v1_configure(surf->lock_surface, | 149 | lock_output_reconfigure(output); |
45 | surf->output->width, surf->output->height); | ||
46 | } | 150 | } |
47 | } | 151 | } |
48 | 152 | ||
49 | static void handle_surface_destroy(struct wl_listener *listener, void *data) { | 153 | static struct sway_session_lock_output *session_lock_output_create( |
50 | struct sway_session_lock_surface *surf = wl_container_of(listener, surf, destroy); | 154 | struct sway_session_lock *lock, struct sway_output *output) { |
51 | wl_list_remove(&surf->map.link); | 155 | struct sway_session_lock_output *lock_output = calloc(1, sizeof(*lock_output)); |
52 | wl_list_remove(&surf->destroy.link); | 156 | if (!lock_output) { |
53 | wl_list_remove(&surf->surface_commit.link); | 157 | sway_log(SWAY_ERROR, "failed to allocate a session lock output"); |
54 | wl_list_remove(&surf->output_mode.link); | 158 | return NULL; |
55 | wl_list_remove(&surf->output_commit.link); | 159 | } |
56 | output_damage_whole(surf->output); | 160 | |
57 | free(surf); | 161 | struct wlr_scene_tree *tree = wlr_scene_tree_create(output->layers.session_lock); |
162 | if (!tree) { | ||
163 | sway_log(SWAY_ERROR, "failed to allocate a session lock output scene tree"); | ||
164 | free(lock_output); | ||
165 | return NULL; | ||
166 | } | ||
167 | |||
168 | struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, (float[4]){ | ||
169 | lock->abandoned ? 1.f : 0.f, | ||
170 | 0.f, | ||
171 | 0.f, | ||
172 | 1.f, | ||
173 | }); | ||
174 | if (!background) { | ||
175 | sway_log(SWAY_ERROR, "failed to allocate a session lock output scene background"); | ||
176 | wlr_scene_node_destroy(&tree->node); | ||
177 | free(lock_output); | ||
178 | return NULL; | ||
179 | } | ||
180 | |||
181 | lock_output->output = output; | ||
182 | lock_output->tree = tree; | ||
183 | lock_output->background = background; | ||
184 | lock_output->lock = lock; | ||
185 | |||
186 | lock_output->destroy.notify = lock_node_handle_destroy; | ||
187 | wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); | ||
188 | |||
189 | lock_output->commit.notify = lock_output_handle_commit; | ||
190 | wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit); | ||
191 | |||
192 | lock_output_reconfigure(lock_output); | ||
193 | |||
194 | wl_list_insert(&lock->outputs, &lock_output->link); | ||
195 | |||
196 | return lock_output; | ||
58 | } | 197 | } |
59 | 198 | ||
60 | static void handle_new_surface(struct wl_listener *listener, void *data) { | 199 | static void sway_session_lock_destroy(struct sway_session_lock* lock) { |
61 | struct wlr_session_lock_surface_v1 *lock_surface = data; | 200 | struct sway_session_lock_output *lock_output, *tmp_lock_output; |
62 | struct sway_session_lock_surface *surf = calloc(1, sizeof(*surf)); | 201 | wl_list_for_each_safe(lock_output, tmp_lock_output, &lock->outputs, link) { |
63 | if (surf == NULL) { | 202 | // destroying the node will also destroy the whole lock output |
64 | return; | 203 | wlr_scene_node_destroy(&lock_output->tree->node); |
65 | } | 204 | } |
66 | 205 | ||
67 | sway_log(SWAY_DEBUG, "new lock layer surface"); | 206 | if (server.session_lock.lock == lock) { |
207 | server.session_lock.lock = NULL; | ||
208 | } | ||
68 | 209 | ||
69 | struct sway_output *output = lock_surface->output->data; | 210 | if (!lock->abandoned) { |
70 | wlr_session_lock_surface_v1_configure(lock_surface, output->width, output->height); | 211 | wl_list_remove(&lock->destroy.link); |
71 | 212 | wl_list_remove(&lock->unlock.link); | |
72 | surf->lock_surface = lock_surface; | 213 | wl_list_remove(&lock->new_surface.link); |
73 | surf->surface = lock_surface->surface; | 214 | } |
74 | surf->output = output; | 215 | |
75 | surf->map.notify = handle_surface_map; | 216 | free(lock); |
76 | wl_signal_add(&lock_surface->events.map, &surf->map); | ||
77 | surf->destroy.notify = handle_surface_destroy; | ||
78 | wl_signal_add(&lock_surface->events.destroy, &surf->destroy); | ||
79 | surf->surface_commit.notify = handle_surface_commit; | ||
80 | wl_signal_add(&surf->surface->events.commit, &surf->surface_commit); | ||
81 | surf->output_mode.notify = handle_output_mode; | ||
82 | wl_signal_add(&output->wlr_output->events.mode, &surf->output_mode); | ||
83 | surf->output_commit.notify = handle_output_commit; | ||
84 | wl_signal_add(&output->wlr_output->events.commit, &surf->output_commit); | ||
85 | } | 217 | } |
86 | 218 | ||
87 | static void handle_unlock(struct wl_listener *listener, void *data) { | 219 | static void handle_unlock(struct wl_listener *listener, void *data) { |
220 | struct sway_session_lock *lock = wl_container_of(listener, lock, unlock); | ||
88 | sway_log(SWAY_DEBUG, "session unlocked"); | 221 | sway_log(SWAY_DEBUG, "session unlocked"); |
89 | server.session_lock.locked = false; | ||
90 | server.session_lock.lock = NULL; | ||
91 | 222 | ||
92 | wl_list_remove(&server.session_lock.lock_new_surface.link); | 223 | sway_session_lock_destroy(lock); |
93 | wl_list_remove(&server.session_lock.lock_unlock.link); | ||
94 | wl_list_remove(&server.session_lock.lock_destroy.link); | ||
95 | 224 | ||
96 | struct sway_seat *seat; | 225 | struct sway_seat *seat; |
97 | wl_list_for_each(seat, &server.input->seats, link) { | 226 | wl_list_for_each(seat, &server.input->seats, link) { |
98 | seat_set_exclusive_client(seat, NULL); | ||
99 | // copied from seat_set_focus_layer -- deduplicate? | 227 | // copied from seat_set_focus_layer -- deduplicate? |
100 | struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); | 228 | struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); |
101 | if (previous) { | 229 | if (previous) { |
@@ -105,31 +233,28 @@ static void handle_unlock(struct wl_listener *listener, void *data) { | |||
105 | } | 233 | } |
106 | } | 234 | } |
107 | 235 | ||
108 | // redraw everything | 236 | // Triggers a refocus of the topmost surface layer if necessary |
237 | // TODO: Make layer surface focus per-output based on cursor position | ||
109 | for (int i = 0; i < root->outputs->length; ++i) { | 238 | for (int i = 0; i < root->outputs->length; ++i) { |
110 | struct sway_output *output = root->outputs->items[i]; | 239 | struct sway_output *output = root->outputs->items[i]; |
111 | output_damage_whole(output); | 240 | arrange_layers(output); |
112 | } | 241 | } |
113 | } | 242 | } |
114 | 243 | ||
115 | static void handle_abandon(struct wl_listener *listener, void *data) { | 244 | static void handle_abandon(struct wl_listener *listener, void *data) { |
245 | struct sway_session_lock *lock = wl_container_of(listener, lock, destroy); | ||
116 | sway_log(SWAY_INFO, "session lock abandoned"); | 246 | sway_log(SWAY_INFO, "session lock abandoned"); |
117 | server.session_lock.lock = NULL; | ||
118 | |||
119 | wl_list_remove(&server.session_lock.lock_new_surface.link); | ||
120 | wl_list_remove(&server.session_lock.lock_unlock.link); | ||
121 | wl_list_remove(&server.session_lock.lock_destroy.link); | ||
122 | 247 | ||
123 | struct sway_seat *seat; | 248 | struct sway_session_lock_output *lock_output; |
124 | wl_list_for_each(seat, &server.input->seats, link) { | 249 | wl_list_for_each(lock_output, &lock->outputs, link) { |
125 | seat->exclusive_client = NULL; | 250 | wlr_scene_rect_set_color(lock_output->background, |
251 | (float[4]){ 1.f, 0.f, 0.f, 1.f }); | ||
126 | } | 252 | } |
127 | 253 | ||
128 | // redraw everything | 254 | lock->abandoned = true; |
129 | for (int i = 0; i < root->outputs->length; ++i) { | 255 | wl_list_remove(&lock->destroy.link); |
130 | struct sway_output *output = root->outputs->items[i]; | 256 | wl_list_remove(&lock->unlock.link); |
131 | output_damage_whole(output); | 257 | wl_list_remove(&lock->new_surface.link); |
132 | } | ||
133 | } | 258 | } |
134 | 259 | ||
135 | static void handle_session_lock(struct wl_listener *listener, void *data) { | 260 | static void handle_session_lock(struct wl_listener *listener, void *data) { |
@@ -137,44 +262,89 @@ static void handle_session_lock(struct wl_listener *listener, void *data) { | |||
137 | struct wl_client *client = wl_resource_get_client(lock->resource); | 262 | struct wl_client *client = wl_resource_get_client(lock->resource); |
138 | 263 | ||
139 | if (server.session_lock.lock) { | 264 | if (server.session_lock.lock) { |
265 | if (server.session_lock.lock->abandoned) { | ||
266 | sway_log(SWAY_INFO, "Replacing abandoned lock"); | ||
267 | sway_session_lock_destroy(server.session_lock.lock); | ||
268 | } else { | ||
269 | sway_log(SWAY_ERROR, "Cannot lock an already locked session"); | ||
270 | wlr_session_lock_v1_destroy(lock); | ||
271 | return; | ||
272 | } | ||
273 | } | ||
274 | |||
275 | struct sway_session_lock *sway_lock = calloc(1, sizeof(*sway_lock)); | ||
276 | if (!sway_lock) { | ||
277 | sway_log(SWAY_ERROR, "failed to allocate a session lock object"); | ||
140 | wlr_session_lock_v1_destroy(lock); | 278 | wlr_session_lock_v1_destroy(lock); |
141 | return; | 279 | return; |
142 | } | 280 | } |
143 | 281 | ||
282 | wl_list_init(&sway_lock->outputs); | ||
283 | |||
144 | sway_log(SWAY_DEBUG, "session locked"); | 284 | sway_log(SWAY_DEBUG, "session locked"); |
145 | server.session_lock.locked = true; | ||
146 | server.session_lock.lock = lock; | ||
147 | 285 | ||
148 | struct sway_seat *seat; | 286 | struct sway_seat *seat; |
149 | wl_list_for_each(seat, &server.input->seats, link) { | 287 | wl_list_for_each(seat, &server.input->seats, link) { |
150 | seat_set_exclusive_client(seat, client); | 288 | seat_unfocus_unless_client(seat, client); |
151 | } | 289 | } |
152 | 290 | ||
153 | wl_signal_add(&lock->events.new_surface, &server.session_lock.lock_new_surface); | 291 | struct sway_output *output; |
154 | wl_signal_add(&lock->events.unlock, &server.session_lock.lock_unlock); | 292 | wl_list_for_each(output, &root->all_outputs, link) { |
155 | wl_signal_add(&lock->events.destroy, &server.session_lock.lock_destroy); | 293 | sway_session_lock_add_output(sway_lock, output); |
294 | } | ||
156 | 295 | ||
157 | wlr_session_lock_v1_send_locked(lock); | 296 | sway_lock->new_surface.notify = handle_new_surface; |
297 | wl_signal_add(&lock->events.new_surface, &sway_lock->new_surface); | ||
298 | sway_lock->unlock.notify = handle_unlock; | ||
299 | wl_signal_add(&lock->events.unlock, &sway_lock->unlock); | ||
300 | sway_lock->destroy.notify = handle_abandon; | ||
301 | wl_signal_add(&lock->events.destroy, &sway_lock->destroy); | ||
158 | 302 | ||
159 | // redraw everything | 303 | wlr_session_lock_v1_send_locked(lock); |
160 | for (int i = 0; i < root->outputs->length; ++i) { | 304 | server.session_lock.lock = sway_lock; |
161 | struct sway_output *output = root->outputs->items[i]; | ||
162 | output_damage_whole(output); | ||
163 | } | ||
164 | } | 305 | } |
165 | 306 | ||
166 | static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { | 307 | static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { |
167 | assert(server.session_lock.lock == NULL); | 308 | // if the server shuts down while a lock is active, destroy the lock |
309 | if (server.session_lock.lock) { | ||
310 | sway_session_lock_destroy(server.session_lock.lock); | ||
311 | } | ||
312 | |||
168 | wl_list_remove(&server.session_lock.new_lock.link); | 313 | wl_list_remove(&server.session_lock.new_lock.link); |
169 | wl_list_remove(&server.session_lock.manager_destroy.link); | 314 | wl_list_remove(&server.session_lock.manager_destroy.link); |
315 | |||
316 | server.session_lock.manager = NULL; | ||
317 | } | ||
318 | |||
319 | void sway_session_lock_add_output(struct sway_session_lock *lock, | ||
320 | struct sway_output *output) { | ||
321 | struct sway_session_lock_output *lock_output = | ||
322 | session_lock_output_create(lock, output); | ||
323 | |||
324 | // if we run out of memory while trying to lock the screen, the best we | ||
325 | // can do is kill the sway process. Security conscious users will have | ||
326 | // the sway session fall back to a login shell. | ||
327 | if (!lock_output) { | ||
328 | sway_log(SWAY_ERROR, "aborting: failed to allocate a lock output"); | ||
329 | abort(); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | bool sway_session_lock_has_surface(struct sway_session_lock *lock, | ||
334 | struct wlr_surface *surface) { | ||
335 | struct sway_session_lock_output *lock_output; | ||
336 | wl_list_for_each(lock_output, &lock->outputs, link) { | ||
337 | if (lock_output->surface && lock_output->surface->surface == surface) { | ||
338 | return true; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | return false; | ||
170 | } | 343 | } |
171 | 344 | ||
172 | void sway_session_lock_init(void) { | 345 | void sway_session_lock_init(void) { |
173 | server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); | 346 | server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); |
174 | 347 | ||
175 | server.session_lock.lock_new_surface.notify = handle_new_surface; | ||
176 | server.session_lock.lock_unlock.notify = handle_unlock; | ||
177 | server.session_lock.lock_destroy.notify = handle_abandon; | ||
178 | server.session_lock.new_lock.notify = handle_session_lock; | 348 | server.session_lock.new_lock.notify = handle_session_lock; |
179 | server.session_lock.manager_destroy.notify = handle_session_lock_destroy; | 349 | server.session_lock.manager_destroy.notify = handle_session_lock_destroy; |
180 | wl_signal_add(&server.session_lock.manager->events.new_lock, | 350 | wl_signal_add(&server.session_lock.manager->events.new_lock, |