diff options
Diffstat (limited to 'sway/lock.c')
-rw-r--r-- | sway/lock.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/sway/lock.c b/sway/lock.c new file mode 100644 index 00000000..8ad9c3f6 --- /dev/null +++ b/sway/lock.c | |||
@@ -0,0 +1,354 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <assert.h> | ||
3 | #include <wlr/types/wlr_scene.h> | ||
4 | #include "log.h" | ||
5 | #include "sway/input/cursor.h" | ||
6 | #include "sway/input/keyboard.h" | ||
7 | #include "sway/input/seat.h" | ||
8 | #include "sway/layers.h" | ||
9 | #include "sway/output.h" | ||
10 | #include "sway/server.h" | ||
11 | |||
12 | struct sway_session_lock_output { | ||
13 | struct wlr_scene_tree *tree; | ||
14 | struct wlr_scene_rect *background; | ||
15 | struct sway_session_lock *lock; | ||
16 | |||
17 | struct sway_output *output; | ||
18 | |||
19 | struct wl_list link; // sway_session_lock::outputs | ||
20 | |||
21 | struct wl_listener destroy; | ||
22 | struct wl_listener commit; | ||
23 | |||
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; | ||
29 | }; | ||
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 | |||
62 | static void handle_surface_map(struct wl_listener *listener, void *data) { | ||
63 | struct sway_session_lock_output *surf = wl_container_of(listener, surf, surface_map); | ||
64 | if (surf->lock->focused == NULL) { | ||
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); | ||
119 | } | ||
120 | |||
121 | static void sway_session_lock_output_destroy(struct sway_session_lock_output *output) { | ||
122 | if (output->surface) { | ||
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); | ||
133 | } | ||
134 | |||
135 | static void lock_node_handle_destroy(struct wl_listener *listener, void *data) { | ||
136 | struct sway_session_lock_output *output = | ||
137 | wl_container_of(listener, output, destroy); | ||
138 | sway_session_lock_output_destroy(output); | ||
139 | } | ||
140 | |||
141 | static void lock_output_handle_commit(struct wl_listener *listener, void *data) { | ||
142 | struct wlr_output_event_commit *event = data; | ||
143 | struct sway_session_lock_output *output = | ||
144 | wl_container_of(listener, output, commit); | ||
145 | if (event->state->committed & ( | ||
146 | WLR_OUTPUT_STATE_MODE | | ||
147 | WLR_OUTPUT_STATE_SCALE | | ||
148 | WLR_OUTPUT_STATE_TRANSFORM)) { | ||
149 | lock_output_reconfigure(output); | ||
150 | } | ||
151 | } | ||
152 | |||
153 | static struct sway_session_lock_output *session_lock_output_create( | ||
154 | struct sway_session_lock *lock, struct sway_output *output) { | ||
155 | struct sway_session_lock_output *lock_output = calloc(1, sizeof(*lock_output)); | ||
156 | if (!lock_output) { | ||
157 | sway_log(SWAY_ERROR, "failed to allocate a session lock output"); | ||
158 | return NULL; | ||
159 | } | ||
160 | |||
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; | ||
197 | } | ||
198 | |||
199 | static void sway_session_lock_destroy(struct sway_session_lock* lock) { | ||
200 | struct sway_session_lock_output *lock_output, *tmp_lock_output; | ||
201 | wl_list_for_each_safe(lock_output, tmp_lock_output, &lock->outputs, link) { | ||
202 | // destroying the node will also destroy the whole lock output | ||
203 | wlr_scene_node_destroy(&lock_output->tree->node); | ||
204 | } | ||
205 | |||
206 | if (server.session_lock.lock == lock) { | ||
207 | server.session_lock.lock = NULL; | ||
208 | } | ||
209 | |||
210 | if (!lock->abandoned) { | ||
211 | wl_list_remove(&lock->destroy.link); | ||
212 | wl_list_remove(&lock->unlock.link); | ||
213 | wl_list_remove(&lock->new_surface.link); | ||
214 | } | ||
215 | |||
216 | free(lock); | ||
217 | } | ||
218 | |||
219 | static void handle_unlock(struct wl_listener *listener, void *data) { | ||
220 | struct sway_session_lock *lock = wl_container_of(listener, lock, unlock); | ||
221 | sway_log(SWAY_DEBUG, "session unlocked"); | ||
222 | |||
223 | sway_session_lock_destroy(lock); | ||
224 | |||
225 | struct sway_seat *seat; | ||
226 | wl_list_for_each(seat, &server.input->seats, link) { | ||
227 | // copied from seat_set_focus_layer -- deduplicate? | ||
228 | struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); | ||
229 | if (previous) { | ||
230 | // Hack to get seat to re-focus the return value of get_focus | ||
231 | seat_set_focus(seat, NULL); | ||
232 | seat_set_focus(seat, previous); | ||
233 | } | ||
234 | } | ||
235 | |||
236 | // Triggers a refocus of the topmost surface layer if necessary | ||
237 | // TODO: Make layer surface focus per-output based on cursor position | ||
238 | for (int i = 0; i < root->outputs->length; ++i) { | ||
239 | struct sway_output *output = root->outputs->items[i]; | ||
240 | arrange_layers(output); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | static void handle_abandon(struct wl_listener *listener, void *data) { | ||
245 | struct sway_session_lock *lock = wl_container_of(listener, lock, destroy); | ||
246 | sway_log(SWAY_INFO, "session lock abandoned"); | ||
247 | |||
248 | struct sway_session_lock_output *lock_output; | ||
249 | wl_list_for_each(lock_output, &lock->outputs, link) { | ||
250 | wlr_scene_rect_set_color(lock_output->background, | ||
251 | (float[4]){ 1.f, 0.f, 0.f, 1.f }); | ||
252 | } | ||
253 | |||
254 | lock->abandoned = true; | ||
255 | wl_list_remove(&lock->destroy.link); | ||
256 | wl_list_remove(&lock->unlock.link); | ||
257 | wl_list_remove(&lock->new_surface.link); | ||
258 | } | ||
259 | |||
260 | static void handle_session_lock(struct wl_listener *listener, void *data) { | ||
261 | struct wlr_session_lock_v1 *lock = data; | ||
262 | struct wl_client *client = wl_resource_get_client(lock->resource); | ||
263 | |||
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"); | ||
278 | wlr_session_lock_v1_destroy(lock); | ||
279 | return; | ||
280 | } | ||
281 | |||
282 | wl_list_init(&sway_lock->outputs); | ||
283 | |||
284 | sway_log(SWAY_DEBUG, "session locked"); | ||
285 | |||
286 | struct sway_seat *seat; | ||
287 | wl_list_for_each(seat, &server.input->seats, link) { | ||
288 | seat_unfocus_unless_client(seat, client); | ||
289 | } | ||
290 | |||
291 | struct sway_output *output; | ||
292 | wl_list_for_each(output, &root->all_outputs, link) { | ||
293 | sway_session_lock_add_output(sway_lock, output); | ||
294 | } | ||
295 | |||
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); | ||
302 | |||
303 | wlr_session_lock_v1_send_locked(lock); | ||
304 | server.session_lock.lock = sway_lock; | ||
305 | } | ||
306 | |||
307 | static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { | ||
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 | |||
313 | wl_list_remove(&server.session_lock.new_lock.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; | ||
343 | } | ||
344 | |||
345 | void sway_session_lock_init(void) { | ||
346 | server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); | ||
347 | |||
348 | server.session_lock.new_lock.notify = handle_session_lock; | ||
349 | server.session_lock.manager_destroy.notify = handle_session_lock_destroy; | ||
350 | wl_signal_add(&server.session_lock.manager->events.new_lock, | ||
351 | &server.session_lock.new_lock); | ||
352 | wl_signal_add(&server.session_lock.manager->events.destroy, | ||
353 | &server.session_lock.manager_destroy); | ||
354 | } | ||