1 #define _POSIX_C_SOURCE 200809L
2 #include <assert.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <tgmath.h>
6 #include <time.h>
7 #include <wayland-server-core.h>
8 #include <wlr/interfaces/wlr_output.h>
9 #include <wlr/render/interface.h>
10 #include <wlr/render/wlr_renderer.h>
11 #include <wlr/types/wlr_box.h>
12 #include <wlr/types/wlr_matrix.h>
13 #include <wlr/types/wlr_output.h>
14 #include <wlr/types/wlr_seat.h>
15 #include <wlr/types/wlr_surface.h>
16 #include <wlr/util/log.h>
17 #include <wlr/util/region.h>
18 #include "util/global.h"
19 #include "util/signal.h"
20
21 #define OUTPUT_VERSION 3
22
send_geometry(struct wl_resource * resource)23 static void send_geometry(struct wl_resource *resource) {
24 struct wlr_output *output = wlr_output_from_resource(resource);
25 wl_output_send_geometry(resource, 0, 0,
26 output->phys_width, output->phys_height, output->subpixel,
27 output->make, output->model, output->transform);
28 }
29
send_current_mode(struct wl_resource * resource)30 static void send_current_mode(struct wl_resource *resource) {
31 struct wlr_output *output = wlr_output_from_resource(resource);
32 if (output->current_mode != NULL) {
33 struct wlr_output_mode *mode = output->current_mode;
34 wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT,
35 mode->width, mode->height, mode->refresh);
36 } else {
37 // Output has no mode
38 wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT, output->width,
39 output->height, output->refresh);
40 }
41 }
42
send_scale(struct wl_resource * resource)43 static void send_scale(struct wl_resource *resource) {
44 struct wlr_output *output = wlr_output_from_resource(resource);
45 uint32_t version = wl_resource_get_version(resource);
46 if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) {
47 wl_output_send_scale(resource, (uint32_t)ceil(output->scale));
48 }
49 }
50
send_done(struct wl_resource * resource)51 static void send_done(struct wl_resource *resource) {
52 uint32_t version = wl_resource_get_version(resource);
53 if (version >= WL_OUTPUT_DONE_SINCE_VERSION) {
54 wl_output_send_done(resource);
55 }
56 }
57
output_handle_resource_destroy(struct wl_resource * resource)58 static void output_handle_resource_destroy(struct wl_resource *resource) {
59 wl_list_remove(wl_resource_get_link(resource));
60 }
61
output_handle_release(struct wl_client * client,struct wl_resource * resource)62 static void output_handle_release(struct wl_client *client,
63 struct wl_resource *resource) {
64 wl_resource_destroy(resource);
65 }
66
67 static const struct wl_output_interface output_impl = {
68 .release = output_handle_release,
69 };
70
output_bind(struct wl_client * wl_client,void * data,uint32_t version,uint32_t id)71 static void output_bind(struct wl_client *wl_client, void *data,
72 uint32_t version, uint32_t id) {
73 // `output` can be NULL if the output global is being destroyed
74 struct wlr_output *output = data;
75
76 struct wl_resource *resource = wl_resource_create(wl_client,
77 &wl_output_interface, version, id);
78 if (resource == NULL) {
79 wl_client_post_no_memory(wl_client);
80 return;
81 }
82 wl_resource_set_implementation(resource, &output_impl, output,
83 output_handle_resource_destroy);
84
85 if (output == NULL) {
86 wl_list_init(wl_resource_get_link(resource));
87 return;
88 }
89
90 wl_list_insert(&output->resources, wl_resource_get_link(resource));
91
92 send_geometry(resource);
93 send_current_mode(resource);
94 send_scale(resource);
95 send_done(resource);
96 }
97
wlr_output_create_global(struct wlr_output * output)98 void wlr_output_create_global(struct wlr_output *output) {
99 if (output->global != NULL) {
100 return;
101 }
102 output->global = wl_global_create(output->display,
103 &wl_output_interface, OUTPUT_VERSION, output, output_bind);
104 if (output->global == NULL) {
105 wlr_log(WLR_ERROR, "Failed to allocate wl_output global");
106 }
107 }
108
wlr_output_destroy_global(struct wlr_output * output)109 void wlr_output_destroy_global(struct wlr_output *output) {
110 if (output->global == NULL) {
111 return;
112 }
113
114 // Make all output resources inert
115 struct wl_resource *resource, *tmp;
116 wl_resource_for_each_safe(resource, tmp, &output->resources) {
117 wl_resource_set_user_data(resource, NULL);
118 wl_list_remove(wl_resource_get_link(resource));
119 wl_list_init(wl_resource_get_link(resource));
120 }
121
122 wlr_global_destroy_safe(output->global, output->display);
123 output->global = NULL;
124 }
125
wlr_output_update_enabled(struct wlr_output * output,bool enabled)126 void wlr_output_update_enabled(struct wlr_output *output, bool enabled) {
127 if (output->enabled == enabled) {
128 return;
129 }
130
131 output->enabled = enabled;
132 wlr_signal_emit_safe(&output->events.enable, output);
133 }
134
output_update_matrix(struct wlr_output * output)135 static void output_update_matrix(struct wlr_output *output) {
136 wlr_matrix_projection(output->transform_matrix, output->width,
137 output->height, output->transform);
138 }
139
wlr_output_enable(struct wlr_output * output,bool enable)140 void wlr_output_enable(struct wlr_output *output, bool enable) {
141 if (output->enabled == enable) {
142 output->pending.committed &= ~WLR_OUTPUT_STATE_ENABLED;
143 return;
144 }
145
146 output->pending.committed |= WLR_OUTPUT_STATE_ENABLED;
147 output->pending.enabled = enable;
148 }
149
output_state_clear_mode(struct wlr_output_state * state)150 static void output_state_clear_mode(struct wlr_output_state *state) {
151 if (!(state->committed & WLR_OUTPUT_STATE_MODE)) {
152 return;
153 }
154
155 state->mode = NULL;
156
157 state->committed &= ~WLR_OUTPUT_STATE_MODE;
158 }
159
wlr_output_set_mode(struct wlr_output * output,struct wlr_output_mode * mode)160 void wlr_output_set_mode(struct wlr_output *output,
161 struct wlr_output_mode *mode) {
162 output_state_clear_mode(&output->pending);
163
164 if (output->current_mode == mode) {
165 return;
166 }
167
168 output->pending.committed |= WLR_OUTPUT_STATE_MODE;
169 output->pending.mode_type = WLR_OUTPUT_STATE_MODE_FIXED;
170 output->pending.mode = mode;
171 }
172
wlr_output_set_custom_mode(struct wlr_output * output,int32_t width,int32_t height,int32_t refresh)173 void wlr_output_set_custom_mode(struct wlr_output *output, int32_t width,
174 int32_t height, int32_t refresh) {
175 output_state_clear_mode(&output->pending);
176
177 if (output->width == width && output->height == height &&
178 output->refresh == refresh) {
179 return;
180 }
181
182 output->pending.committed |= WLR_OUTPUT_STATE_MODE;
183 output->pending.mode_type = WLR_OUTPUT_STATE_MODE_CUSTOM;
184 output->pending.custom_mode.width = width;
185 output->pending.custom_mode.height = height;
186 output->pending.custom_mode.refresh = refresh;
187 }
188
wlr_output_update_mode(struct wlr_output * output,struct wlr_output_mode * mode)189 void wlr_output_update_mode(struct wlr_output *output,
190 struct wlr_output_mode *mode) {
191 output->current_mode = mode;
192 if (mode != NULL) {
193 wlr_output_update_custom_mode(output, mode->width, mode->height,
194 mode->refresh);
195 } else {
196 wlr_output_update_custom_mode(output, 0, 0, 0);
197 }
198 }
199
wlr_output_update_custom_mode(struct wlr_output * output,int32_t width,int32_t height,int32_t refresh)200 void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width,
201 int32_t height, int32_t refresh) {
202 if (output->width == width && output->height == height &&
203 output->refresh == refresh) {
204 return;
205 }
206
207 output->width = width;
208 output->height = height;
209 output_update_matrix(output);
210
211 output->refresh = refresh;
212
213 struct wl_resource *resource;
214 wl_resource_for_each(resource, &output->resources) {
215 send_current_mode(resource);
216 }
217 wlr_output_schedule_done(output);
218
219 wlr_signal_emit_safe(&output->events.mode, output);
220 }
221
wlr_output_set_transform(struct wlr_output * output,enum wl_output_transform transform)222 void wlr_output_set_transform(struct wlr_output *output,
223 enum wl_output_transform transform) {
224 if (output->transform == transform) {
225 output->pending.committed &= ~WLR_OUTPUT_STATE_TRANSFORM;
226 return;
227 }
228
229 output->pending.committed |= WLR_OUTPUT_STATE_TRANSFORM;
230 output->pending.transform = transform;
231 }
232
wlr_output_set_scale(struct wlr_output * output,float scale)233 void wlr_output_set_scale(struct wlr_output *output, float scale) {
234 if (output->scale == scale) {
235 output->pending.committed &= ~WLR_OUTPUT_STATE_SCALE;
236 return;
237 }
238
239 output->pending.committed |= WLR_OUTPUT_STATE_SCALE;
240 output->pending.scale = scale;
241 }
242
wlr_output_enable_adaptive_sync(struct wlr_output * output,bool enabled)243 void wlr_output_enable_adaptive_sync(struct wlr_output *output, bool enabled) {
244 bool currently_enabled =
245 output->adaptive_sync_status != WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
246 if (currently_enabled == enabled) {
247 output->pending.committed &= ~WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
248 return;
249 }
250
251 output->pending.committed |= WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
252 output->pending.adaptive_sync_enabled = enabled;
253 }
254
wlr_output_set_subpixel(struct wlr_output * output,enum wl_output_subpixel subpixel)255 void wlr_output_set_subpixel(struct wlr_output *output,
256 enum wl_output_subpixel subpixel) {
257 if (output->subpixel == subpixel) {
258 return;
259 }
260
261 output->subpixel = subpixel;
262
263 struct wl_resource *resource;
264 wl_resource_for_each(resource, &output->resources) {
265 send_geometry(resource);
266 }
267 wlr_output_schedule_done(output);
268 }
269
wlr_output_set_description(struct wlr_output * output,const char * desc)270 void wlr_output_set_description(struct wlr_output *output, const char *desc) {
271 if (output->description != NULL && desc != NULL &&
272 strcmp(output->description, desc) == 0) {
273 return;
274 }
275
276 free(output->description);
277 if (desc != NULL) {
278 output->description = strdup(desc);
279 } else {
280 output->description = NULL;
281 }
282
283 wlr_signal_emit_safe(&output->events.description, output);
284 }
285
schedule_done_handle_idle_timer(void * data)286 static void schedule_done_handle_idle_timer(void *data) {
287 struct wlr_output *output = data;
288 output->idle_done = NULL;
289
290 struct wl_resource *resource;
291 wl_resource_for_each(resource, &output->resources) {
292 uint32_t version = wl_resource_get_version(resource);
293 if (version >= WL_OUTPUT_DONE_SINCE_VERSION) {
294 wl_output_send_done(resource);
295 }
296 }
297 }
298
wlr_output_schedule_done(struct wlr_output * output)299 void wlr_output_schedule_done(struct wlr_output *output) {
300 if (output->idle_done != NULL) {
301 return; // Already scheduled
302 }
303
304 struct wl_event_loop *ev = wl_display_get_event_loop(output->display);
305 output->idle_done =
306 wl_event_loop_add_idle(ev, schedule_done_handle_idle_timer, output);
307 }
308
handle_display_destroy(struct wl_listener * listener,void * data)309 static void handle_display_destroy(struct wl_listener *listener, void *data) {
310 struct wlr_output *output =
311 wl_container_of(listener, output, display_destroy);
312 wlr_output_destroy_global(output);
313 }
314
wlr_output_init(struct wlr_output * output,struct wlr_backend * backend,const struct wlr_output_impl * impl,struct wl_display * display)315 void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend,
316 const struct wlr_output_impl *impl, struct wl_display *display) {
317 assert(impl->attach_render && impl->rollback_render && impl->commit);
318 if (impl->set_cursor || impl->move_cursor) {
319 assert(impl->set_cursor && impl->move_cursor);
320 }
321 output->backend = backend;
322 output->impl = impl;
323 output->display = display;
324 wl_list_init(&output->modes);
325 output->transform = WL_OUTPUT_TRANSFORM_NORMAL;
326 output->scale = 1;
327 output->commit_seq = 0;
328 wl_list_init(&output->cursors);
329 wl_list_init(&output->resources);
330 wl_signal_init(&output->events.frame);
331 wl_signal_init(&output->events.damage);
332 wl_signal_init(&output->events.needs_frame);
333 wl_signal_init(&output->events.precommit);
334 wl_signal_init(&output->events.commit);
335 wl_signal_init(&output->events.present);
336 wl_signal_init(&output->events.enable);
337 wl_signal_init(&output->events.mode);
338 wl_signal_init(&output->events.scale);
339 wl_signal_init(&output->events.transform);
340 wl_signal_init(&output->events.description);
341 wl_signal_init(&output->events.destroy);
342 pixman_region32_init(&output->pending.damage);
343
344 const char *no_hardware_cursors = getenv("WLR_NO_HARDWARE_CURSORS");
345 if (no_hardware_cursors != NULL && strcmp(no_hardware_cursors, "1") == 0) {
346 wlr_log(WLR_DEBUG,
347 "WLR_NO_HARDWARE_CURSORS set, forcing software cursors");
348 output->software_cursor_locks = 1;
349 }
350
351 output->display_destroy.notify = handle_display_destroy;
352 wl_display_add_destroy_listener(display, &output->display_destroy);
353
354 output->frame_pending = true;
355 }
356
wlr_output_destroy(struct wlr_output * output)357 void wlr_output_destroy(struct wlr_output *output) {
358 if (!output) {
359 return;
360 }
361
362 wl_list_remove(&output->display_destroy.link);
363 wlr_output_destroy_global(output);
364
365 wlr_signal_emit_safe(&output->events.destroy, output);
366
367 // The backend is responsible for free-ing the list of modes
368
369 struct wlr_output_cursor *cursor, *tmp_cursor;
370 wl_list_for_each_safe(cursor, tmp_cursor, &output->cursors, link) {
371 wlr_output_cursor_destroy(cursor);
372 }
373
374 if (output->idle_frame != NULL) {
375 wl_event_source_remove(output->idle_frame);
376 }
377
378 if (output->idle_done != NULL) {
379 wl_event_source_remove(output->idle_done);
380 }
381
382 free(output->description);
383
384 pixman_region32_fini(&output->pending.damage);
385
386 if (output->impl && output->impl->destroy) {
387 output->impl->destroy(output);
388 } else {
389 free(output);
390 }
391 }
392
wlr_output_transformed_resolution(struct wlr_output * output,int * width,int * height)393 void wlr_output_transformed_resolution(struct wlr_output *output,
394 int *width, int *height) {
395 if (output->transform % 2 == 0) {
396 *width = output->width;
397 *height = output->height;
398 } else {
399 *width = output->height;
400 *height = output->width;
401 }
402 }
403
wlr_output_effective_resolution(struct wlr_output * output,int * width,int * height)404 void wlr_output_effective_resolution(struct wlr_output *output,
405 int *width, int *height) {
406 wlr_output_transformed_resolution(output, width, height);
407 *width /= output->scale;
408 *height /= output->scale;
409 }
410
wlr_output_preferred_mode(struct wlr_output * output)411 struct wlr_output_mode *wlr_output_preferred_mode(struct wlr_output *output) {
412 if (wl_list_empty(&output->modes)) {
413 return NULL;
414 }
415
416 struct wlr_output_mode *mode;
417 wl_list_for_each(mode, &output->modes, link) {
418 if (mode->preferred) {
419 return mode;
420 }
421 }
422
423 // No preferred mode, choose the last one
424 return wl_container_of(output->modes.prev, mode, link);
425 }
426
output_state_clear_buffer(struct wlr_output_state * state)427 static void output_state_clear_buffer(struct wlr_output_state *state) {
428 if (!(state->committed & WLR_OUTPUT_STATE_BUFFER)) {
429 return;
430 }
431
432 wlr_buffer_unlock(state->buffer);
433 state->buffer = NULL;
434
435 state->committed &= ~WLR_OUTPUT_STATE_BUFFER;
436 }
437
wlr_output_attach_render(struct wlr_output * output,int * buffer_age)438 bool wlr_output_attach_render(struct wlr_output *output, int *buffer_age) {
439 if (!output->impl->attach_render(output, buffer_age)) {
440 return false;
441 }
442
443 output_state_clear_buffer(&output->pending);
444 output->pending.committed |= WLR_OUTPUT_STATE_BUFFER;
445 output->pending.buffer_type = WLR_OUTPUT_STATE_BUFFER_RENDER;
446 return true;
447 }
448
wlr_output_preferred_read_format(struct wlr_output * output,enum wl_shm_format * fmt)449 bool wlr_output_preferred_read_format(struct wlr_output *output,
450 enum wl_shm_format *fmt) {
451 struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
452 if (!renderer->impl->preferred_read_format || !renderer->impl->read_pixels) {
453 return false;
454 }
455
456 if (!output->impl->attach_render(output, NULL)) {
457 return false;
458 }
459 *fmt = renderer->impl->preferred_read_format(renderer);
460 output->impl->rollback_render(output);
461 return true;
462 }
463
wlr_output_set_damage(struct wlr_output * output,pixman_region32_t * damage)464 void wlr_output_set_damage(struct wlr_output *output,
465 pixman_region32_t *damage) {
466 pixman_region32_intersect_rect(&output->pending.damage, damage,
467 0, 0, output->width, output->height);
468 output->pending.committed |= WLR_OUTPUT_STATE_DAMAGE;
469 }
470
output_state_clear_gamma_lut(struct wlr_output_state * state)471 static void output_state_clear_gamma_lut(struct wlr_output_state *state) {
472 free(state->gamma_lut);
473 state->gamma_lut = NULL;
474 state->committed &= ~WLR_OUTPUT_STATE_GAMMA_LUT;
475 }
476
output_state_clear(struct wlr_output_state * state)477 static void output_state_clear(struct wlr_output_state *state) {
478 output_state_clear_buffer(state);
479 output_state_clear_gamma_lut(state);
480 pixman_region32_clear(&state->damage);
481 state->committed = 0;
482 }
483
output_pending_resolution(struct wlr_output * output,int * width,int * height)484 static void output_pending_resolution(struct wlr_output *output, int *width,
485 int *height) {
486 if (output->pending.committed & WLR_OUTPUT_STATE_MODE) {
487 switch (output->pending.mode_type) {
488 case WLR_OUTPUT_STATE_MODE_FIXED:
489 *width = output->pending.mode->width;
490 *height = output->pending.mode->height;
491 return;
492 case WLR_OUTPUT_STATE_MODE_CUSTOM:
493 *width = output->pending.custom_mode.width;
494 *height = output->pending.custom_mode.height;
495 return;
496 }
497 abort();
498 } else {
499 *width = output->width;
500 *height = output->height;
501 }
502 }
503
output_basic_test(struct wlr_output * output)504 static bool output_basic_test(struct wlr_output *output) {
505 if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
506 if (output->frame_pending) {
507 wlr_log(WLR_DEBUG, "Tried to commit a buffer while a frame is pending");
508 return false;
509 }
510
511 if (output->pending.buffer_type == WLR_OUTPUT_STATE_BUFFER_SCANOUT) {
512 if (output->attach_render_locks > 0) {
513 return false;
514 }
515
516 // If the output has at least one software cursor, refuse to attach the
517 // buffer
518 struct wlr_output_cursor *cursor;
519 wl_list_for_each(cursor, &output->cursors, link) {
520 if (cursor->enabled && cursor->visible &&
521 cursor != output->hardware_cursor) {
522 return false;
523 }
524 }
525
526 // If the size doesn't match, reject buffer (scaling is not
527 // supported)
528 int pending_width, pending_height;
529 output_pending_resolution(output, &pending_width, &pending_height);
530 if (output->pending.buffer->width != pending_width ||
531 output->pending.buffer->height != pending_height) {
532 return false;
533 }
534 }
535 }
536
537 bool enabled = output->enabled;
538 if (output->pending.committed & WLR_OUTPUT_STATE_ENABLED) {
539 enabled = output->pending.enabled;
540 }
541
542 if (!enabled && output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
543 wlr_log(WLR_DEBUG, "Tried to commit a buffer on a disabled output");
544 return false;
545 }
546 if (!enabled && output->pending.committed & WLR_OUTPUT_STATE_MODE) {
547 wlr_log(WLR_DEBUG, "Tried to modeset a disabled output");
548 return false;
549 }
550 if (!enabled && output->pending.committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) {
551 wlr_log(WLR_DEBUG, "Tried to enable adaptive sync on a disabled output");
552 return false;
553 }
554 if (!enabled && output->pending.committed & WLR_OUTPUT_STATE_GAMMA_LUT) {
555 wlr_log(WLR_DEBUG, "Tried to set the gamma lut on a disabled output");
556 return false;
557 }
558
559 return true;
560 }
561
wlr_output_test(struct wlr_output * output)562 bool wlr_output_test(struct wlr_output *output) {
563 if (!output_basic_test(output)) {
564 return false;
565 }
566 return output->impl->test(output);
567 }
568
wlr_output_commit(struct wlr_output * output)569 bool wlr_output_commit(struct wlr_output *output) {
570 if (!output_basic_test(output)) {
571 wlr_log(WLR_ERROR, "Basic output test failed");
572 return false;
573 }
574
575 if ((output->pending.committed & WLR_OUTPUT_STATE_BUFFER) &&
576 output->idle_frame != NULL) {
577 wl_event_source_remove(output->idle_frame);
578 output->idle_frame = NULL;
579 }
580
581 struct timespec now;
582 clock_gettime(CLOCK_MONOTONIC, &now);
583
584 struct wlr_output_event_precommit pre_event = {
585 .output = output,
586 .when = &now,
587 };
588 wlr_signal_emit_safe(&output->events.precommit, &pre_event);
589
590 if (!output->impl->commit(output)) {
591 output_state_clear(&output->pending);
592 return false;
593 }
594
595 if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
596 struct wlr_output_cursor *cursor;
597 wl_list_for_each(cursor, &output->cursors, link) {
598 if (!cursor->enabled || !cursor->visible || cursor->surface == NULL) {
599 continue;
600 }
601 wlr_surface_send_frame_done(cursor->surface, &now);
602 }
603 }
604
605 output->commit_seq++;
606
607 struct wlr_output_event_commit event = {
608 .output = output,
609 .committed = output->pending.committed,
610 .when = &now,
611 };
612 wlr_signal_emit_safe(&output->events.commit, &event);
613
614 bool scale_updated = output->pending.committed & WLR_OUTPUT_STATE_SCALE;
615 if (scale_updated) {
616 output->scale = output->pending.scale;
617 wlr_signal_emit_safe(&output->events.scale, output);
618 }
619
620 if (output->pending.committed & WLR_OUTPUT_STATE_TRANSFORM) {
621 output->transform = output->pending.transform;
622 output_update_matrix(output);
623 wlr_signal_emit_safe(&output->events.transform, output);
624 }
625
626 bool geometry_updated = output->pending.committed &
627 (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM);
628 if (geometry_updated || scale_updated) {
629 struct wl_resource *resource;
630 wl_resource_for_each(resource, &output->resources) {
631 if (geometry_updated) {
632 send_geometry(resource);
633 }
634 if (scale_updated) {
635 send_scale(resource);
636 }
637 }
638 wlr_output_schedule_done(output);
639 }
640
641 if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
642 output->frame_pending = true;
643 output->needs_frame = false;
644 }
645
646 output_state_clear(&output->pending);
647 return true;
648 }
649
wlr_output_rollback(struct wlr_output * output)650 void wlr_output_rollback(struct wlr_output *output) {
651 if (output->impl->rollback_render &&
652 (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) &&
653 output->pending.buffer_type == WLR_OUTPUT_STATE_BUFFER_RENDER) {
654 output->impl->rollback_render(output);
655 }
656
657 output_state_clear(&output->pending);
658 }
659
wlr_output_attach_buffer(struct wlr_output * output,struct wlr_buffer * buffer)660 void wlr_output_attach_buffer(struct wlr_output *output,
661 struct wlr_buffer *buffer) {
662 output_state_clear_buffer(&output->pending);
663 output->pending.committed |= WLR_OUTPUT_STATE_BUFFER;
664 output->pending.buffer_type = WLR_OUTPUT_STATE_BUFFER_SCANOUT;
665 output->pending.buffer = wlr_buffer_lock(buffer);
666 }
667
wlr_output_send_frame(struct wlr_output * output)668 void wlr_output_send_frame(struct wlr_output *output) {
669 output->frame_pending = false;
670 wlr_signal_emit_safe(&output->events.frame, output);
671 }
672
schedule_frame_handle_idle_timer(void * data)673 static void schedule_frame_handle_idle_timer(void *data) {
674 struct wlr_output *output = data;
675 output->idle_frame = NULL;
676 if (!output->frame_pending) {
677 wlr_output_send_frame(output);
678 }
679 }
680
wlr_output_schedule_frame(struct wlr_output * output)681 void wlr_output_schedule_frame(struct wlr_output *output) {
682 // Make sure the compositor commits a new frame. This is necessary to make
683 // clients which ask for frame callbacks without submitting a new buffer
684 // work.
685 wlr_output_update_needs_frame(output);
686
687 if (output->frame_pending || output->idle_frame != NULL) {
688 return;
689 }
690
691 // We're using an idle timer here in case a buffer swap happens right after
692 // this function is called
693 struct wl_event_loop *ev = wl_display_get_event_loop(output->display);
694 output->idle_frame =
695 wl_event_loop_add_idle(ev, schedule_frame_handle_idle_timer, output);
696 }
697
wlr_output_send_present(struct wlr_output * output,struct wlr_output_event_present * event)698 void wlr_output_send_present(struct wlr_output *output,
699 struct wlr_output_event_present *event) {
700 struct wlr_output_event_present _event = {0};
701 if (event == NULL) {
702 event = &_event;
703 event->commit_seq = output->commit_seq + 1;
704 }
705
706 event->output = output;
707
708 struct timespec now;
709 if (event->when == NULL) {
710 clockid_t clock = wlr_backend_get_presentation_clock(output->backend);
711 errno = 0;
712 if (clock_gettime(clock, &now) != 0) {
713 wlr_log_errno(WLR_ERROR, "failed to send output present event: "
714 "failed to read clock");
715 return;
716 }
717 event->when = &now;
718 }
719
720 wlr_signal_emit_safe(&output->events.present, event);
721 }
722
wlr_output_set_gamma(struct wlr_output * output,size_t size,const uint16_t * r,const uint16_t * g,const uint16_t * b)723 void wlr_output_set_gamma(struct wlr_output *output, size_t size,
724 const uint16_t *r, const uint16_t *g, const uint16_t *b) {
725 output_state_clear_gamma_lut(&output->pending);
726
727 output->pending.gamma_lut_size = size;
728 output->pending.gamma_lut = malloc(3 * size * sizeof(uint16_t));
729 if (output->pending.gamma_lut == NULL) {
730 wlr_log_errno(WLR_ERROR, "Allocation failed");
731 return;
732 }
733 memcpy(output->pending.gamma_lut, r, size * sizeof(uint16_t));
734 memcpy(output->pending.gamma_lut + size, g, size * sizeof(uint16_t));
735 memcpy(output->pending.gamma_lut + 2 * size, b, size * sizeof(uint16_t));
736
737 output->pending.committed |= WLR_OUTPUT_STATE_GAMMA_LUT;
738 }
739
wlr_output_get_gamma_size(struct wlr_output * output)740 size_t wlr_output_get_gamma_size(struct wlr_output *output) {
741 if (!output->impl->get_gamma_size) {
742 return 0;
743 }
744 return output->impl->get_gamma_size(output);
745 }
746
wlr_output_export_dmabuf(struct wlr_output * output,struct wlr_dmabuf_attributes * attribs)747 bool wlr_output_export_dmabuf(struct wlr_output *output,
748 struct wlr_dmabuf_attributes *attribs) {
749 if (!output->impl->export_dmabuf) {
750 return false;
751 }
752 return output->impl->export_dmabuf(output, attribs);
753 }
754
wlr_output_update_needs_frame(struct wlr_output * output)755 void wlr_output_update_needs_frame(struct wlr_output *output) {
756 if (output->needs_frame) {
757 return;
758 }
759 output->needs_frame = true;
760 wlr_signal_emit_safe(&output->events.needs_frame, output);
761 }
762
wlr_output_damage_whole(struct wlr_output * output)763 void wlr_output_damage_whole(struct wlr_output *output) {
764 int width, height;
765 wlr_output_transformed_resolution(output, &width, &height);
766
767 pixman_region32_t damage;
768 pixman_region32_init_rect(&damage, 0, 0, width, height);
769
770 struct wlr_output_event_damage event = {
771 .output = output,
772 .damage = &damage,
773 };
774 wlr_signal_emit_safe(&output->events.damage, &event);
775
776 pixman_region32_fini(&damage);
777 }
778
wlr_output_from_resource(struct wl_resource * resource)779 struct wlr_output *wlr_output_from_resource(struct wl_resource *resource) {
780 assert(wl_resource_instance_of(resource, &wl_output_interface,
781 &output_impl));
782 return wl_resource_get_user_data(resource);
783 }
784
wlr_output_lock_attach_render(struct wlr_output * output,bool lock)785 void wlr_output_lock_attach_render(struct wlr_output *output, bool lock) {
786 if (lock) {
787 ++output->attach_render_locks;
788 } else {
789 assert(output->attach_render_locks > 0);
790 --output->attach_render_locks;
791 }
792 wlr_log(WLR_DEBUG, "%s direct scan-out on output '%s' (locks: %d)",
793 lock ? "Disabling" : "Enabling", output->name,
794 output->attach_render_locks);
795 }
796
797 static void output_cursor_damage_whole(struct wlr_output_cursor *cursor);
798
wlr_output_lock_software_cursors(struct wlr_output * output,bool lock)799 void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock) {
800 if (lock) {
801 ++output->software_cursor_locks;
802 } else {
803 assert(output->software_cursor_locks > 0);
804 --output->software_cursor_locks;
805 }
806 wlr_log(WLR_DEBUG, "%s hardware cursors on output '%s' (locks: %d)",
807 lock ? "Disabling" : "Enabling", output->name,
808 output->software_cursor_locks);
809
810 if (output->software_cursor_locks > 0 && output->hardware_cursor != NULL) {
811 assert(output->impl->set_cursor);
812 output->impl->set_cursor(output, NULL, 1,
813 WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
814 output_cursor_damage_whole(output->hardware_cursor);
815 output->hardware_cursor = NULL;
816 }
817
818 // If it's possible to use hardware cursors again, don't switch immediately
819 // since a recorder is likely to lock software cursors for the next frame
820 // again.
821 }
822
output_scissor(struct wlr_output * output,pixman_box32_t * rect)823 static void output_scissor(struct wlr_output *output, pixman_box32_t *rect) {
824 struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
825 assert(renderer);
826
827 struct wlr_box box = {
828 .x = rect->x1,
829 .y = rect->y1,
830 .width = rect->x2 - rect->x1,
831 .height = rect->y2 - rect->y1,
832 };
833
834 int ow, oh;
835 wlr_output_transformed_resolution(output, &ow, &oh);
836
837 enum wl_output_transform transform =
838 wlr_output_transform_invert(output->transform);
839 wlr_box_transform(&box, &box, transform, ow, oh);
840
841 wlr_renderer_scissor(renderer, &box);
842 }
843
844 static void output_cursor_get_box(struct wlr_output_cursor *cursor,
845 struct wlr_box *box);
846
output_cursor_render(struct wlr_output_cursor * cursor,pixman_region32_t * damage)847 static void output_cursor_render(struct wlr_output_cursor *cursor,
848 pixman_region32_t *damage) {
849 struct wlr_renderer *renderer =
850 wlr_backend_get_renderer(cursor->output->backend);
851 assert(renderer);
852
853 struct wlr_texture *texture = cursor->texture;
854 if (cursor->surface != NULL) {
855 texture = wlr_surface_get_texture(cursor->surface);
856 }
857 if (texture == NULL) {
858 return;
859 }
860
861 struct wlr_box box;
862 output_cursor_get_box(cursor, &box);
863
864 pixman_region32_t surface_damage;
865 pixman_region32_init(&surface_damage);
866 pixman_region32_union_rect(&surface_damage, &surface_damage, box.x, box.y,
867 box.width, box.height);
868 pixman_region32_intersect(&surface_damage, &surface_damage, damage);
869 if (!pixman_region32_not_empty(&surface_damage)) {
870 goto surface_damage_finish;
871 }
872
873 float matrix[9];
874 wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
875 cursor->output->transform_matrix);
876
877 int nrects;
878 pixman_box32_t *rects = pixman_region32_rectangles(&surface_damage, &nrects);
879 for (int i = 0; i < nrects; ++i) {
880 output_scissor(cursor->output, &rects[i]);
881 wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f);
882 }
883 wlr_renderer_scissor(renderer, NULL);
884
885 surface_damage_finish:
886 pixman_region32_fini(&surface_damage);
887 }
888
wlr_output_render_software_cursors(struct wlr_output * output,pixman_region32_t * damage)889 void wlr_output_render_software_cursors(struct wlr_output *output,
890 pixman_region32_t *damage) {
891 int width, height;
892 wlr_output_transformed_resolution(output, &width, &height);
893
894 pixman_region32_t render_damage;
895 pixman_region32_init(&render_damage);
896 pixman_region32_union_rect(&render_damage, &render_damage, 0, 0,
897 width, height);
898 if (damage != NULL) {
899 // Damage tracking supported
900 pixman_region32_intersect(&render_damage, &render_damage, damage);
901 }
902
903 if (pixman_region32_not_empty(&render_damage)) {
904 struct wlr_output_cursor *cursor;
905 wl_list_for_each(cursor, &output->cursors, link) {
906 if (!cursor->enabled || !cursor->visible ||
907 output->hardware_cursor == cursor) {
908 continue;
909 }
910 output_cursor_render(cursor, &render_damage);
911 }
912 }
913
914 pixman_region32_fini(&render_damage);
915 }
916
917
918 /**
919 * Returns the cursor box, scaled for its output.
920 */
output_cursor_get_box(struct wlr_output_cursor * cursor,struct wlr_box * box)921 static void output_cursor_get_box(struct wlr_output_cursor *cursor,
922 struct wlr_box *box) {
923 box->x = cursor->x - cursor->hotspot_x;
924 box->y = cursor->y - cursor->hotspot_y;
925 box->width = cursor->width;
926 box->height = cursor->height;
927 }
928
output_cursor_damage_whole(struct wlr_output_cursor * cursor)929 static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) {
930 struct wlr_box box;
931 output_cursor_get_box(cursor, &box);
932
933 pixman_region32_t damage;
934 pixman_region32_init_rect(&damage, box.x, box.y, box.width, box.height);
935
936 struct wlr_output_event_damage event = {
937 .output = cursor->output,
938 .damage = &damage,
939 };
940 wlr_signal_emit_safe(&cursor->output->events.damage, &event);
941
942 pixman_region32_fini(&damage);
943 }
944
output_cursor_reset(struct wlr_output_cursor * cursor)945 static void output_cursor_reset(struct wlr_output_cursor *cursor) {
946 if (cursor->output->hardware_cursor != cursor) {
947 output_cursor_damage_whole(cursor);
948 }
949 if (cursor->surface != NULL) {
950 wl_list_remove(&cursor->surface_commit.link);
951 wl_list_remove(&cursor->surface_destroy.link);
952 if (cursor->visible) {
953 wlr_surface_send_leave(cursor->surface, cursor->output);
954 }
955 cursor->surface = NULL;
956 }
957 }
958
output_cursor_update_visible(struct wlr_output_cursor * cursor)959 static void output_cursor_update_visible(struct wlr_output_cursor *cursor) {
960 struct wlr_box output_box;
961 output_box.x = output_box.y = 0;
962 wlr_output_transformed_resolution(cursor->output, &output_box.width,
963 &output_box.height);
964
965 struct wlr_box cursor_box;
966 output_cursor_get_box(cursor, &cursor_box);
967
968 struct wlr_box intersection;
969 bool visible =
970 wlr_box_intersection(&intersection, &output_box, &cursor_box);
971
972 if (cursor->surface != NULL) {
973 if (cursor->visible && !visible) {
974 wlr_surface_send_leave(cursor->surface, cursor->output);
975 }
976 if (!cursor->visible && visible) {
977 wlr_surface_send_enter(cursor->surface, cursor->output);
978 }
979 }
980
981 cursor->visible = visible;
982 }
983
output_cursor_attempt_hardware(struct wlr_output_cursor * cursor)984 static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) {
985 float scale = cursor->output->scale;
986 enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
987 struct wlr_texture *texture = cursor->texture;
988 if (cursor->surface != NULL) {
989 texture = wlr_surface_get_texture(cursor->surface);
990 scale = cursor->surface->current.scale;
991 transform = cursor->surface->current.transform;
992 }
993
994 if (cursor->output->software_cursor_locks > 0) {
995 return false;
996 }
997
998 struct wlr_output_cursor *hwcur = cursor->output->hardware_cursor;
999 if (cursor->output->impl->set_cursor && (hwcur == NULL || hwcur == cursor)) {
1000 // If the cursor was hidden or was a software cursor, the hardware
1001 // cursor position is outdated
1002 assert(cursor->output->impl->move_cursor);
1003 cursor->output->impl->move_cursor(cursor->output,
1004 (int)cursor->x, (int)cursor->y);
1005 if (cursor->output->impl->set_cursor(cursor->output, texture,
1006 scale, transform, cursor->hotspot_x, cursor->hotspot_y, true)) {
1007 cursor->output->hardware_cursor = cursor;
1008 return true;
1009 }
1010 }
1011 return false;
1012 }
1013
wlr_output_cursor_set_image(struct wlr_output_cursor * cursor,const uint8_t * pixels,int32_t stride,uint32_t width,uint32_t height,int32_t hotspot_x,int32_t hotspot_y)1014 bool wlr_output_cursor_set_image(struct wlr_output_cursor *cursor,
1015 const uint8_t *pixels, int32_t stride, uint32_t width, uint32_t height,
1016 int32_t hotspot_x, int32_t hotspot_y) {
1017 struct wlr_renderer *renderer =
1018 wlr_backend_get_renderer(cursor->output->backend);
1019 if (!renderer) {
1020 // if the backend has no renderer, we can't draw a cursor, but this is
1021 // actually okay, for ex. with the noop backend
1022 return true;
1023 }
1024
1025 output_cursor_reset(cursor);
1026
1027 cursor->width = width;
1028 cursor->height = height;
1029 cursor->hotspot_x = hotspot_x;
1030 cursor->hotspot_y = hotspot_y;
1031 output_cursor_update_visible(cursor);
1032
1033 wlr_texture_destroy(cursor->texture);
1034 cursor->texture = NULL;
1035
1036 cursor->enabled = false;
1037 if (pixels != NULL) {
1038 cursor->texture = wlr_texture_from_pixels(renderer,
1039 WL_SHM_FORMAT_ARGB8888, stride, width, height, pixels);
1040 if (cursor->texture == NULL) {
1041 return false;
1042 }
1043 cursor->enabled = true;
1044 }
1045
1046 if (output_cursor_attempt_hardware(cursor)) {
1047 return true;
1048 }
1049
1050 wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'",
1051 cursor->output->name);
1052 output_cursor_damage_whole(cursor);
1053 return true;
1054 }
1055
output_cursor_commit(struct wlr_output_cursor * cursor,bool update_hotspot)1056 static void output_cursor_commit(struct wlr_output_cursor *cursor,
1057 bool update_hotspot) {
1058 if (cursor->output->hardware_cursor != cursor) {
1059 output_cursor_damage_whole(cursor);
1060 }
1061
1062 struct wlr_surface *surface = cursor->surface;
1063 assert(surface != NULL);
1064
1065 // Some clients commit a cursor surface with a NULL buffer to hide it.
1066 cursor->enabled = wlr_surface_has_buffer(surface);
1067 cursor->width = surface->current.width * cursor->output->scale;
1068 cursor->height = surface->current.height * cursor->output->scale;
1069 output_cursor_update_visible(cursor);
1070 if (update_hotspot) {
1071 cursor->hotspot_x -= surface->current.dx * cursor->output->scale;
1072 cursor->hotspot_y -= surface->current.dy * cursor->output->scale;
1073 }
1074
1075 if (output_cursor_attempt_hardware(cursor)) {
1076 return;
1077 }
1078
1079 // Fallback to software cursor
1080 output_cursor_damage_whole(cursor);
1081 }
1082
output_cursor_handle_commit(struct wl_listener * listener,void * data)1083 static void output_cursor_handle_commit(struct wl_listener *listener,
1084 void *data) {
1085 struct wlr_output_cursor *cursor =
1086 wl_container_of(listener, cursor, surface_commit);
1087 output_cursor_commit(cursor, true);
1088 }
1089
output_cursor_handle_destroy(struct wl_listener * listener,void * data)1090 static void output_cursor_handle_destroy(struct wl_listener *listener,
1091 void *data) {
1092 struct wlr_output_cursor *cursor = wl_container_of(listener, cursor,
1093 surface_destroy);
1094 output_cursor_reset(cursor);
1095 }
1096
wlr_output_cursor_set_surface(struct wlr_output_cursor * cursor,struct wlr_surface * surface,int32_t hotspot_x,int32_t hotspot_y)1097 void wlr_output_cursor_set_surface(struct wlr_output_cursor *cursor,
1098 struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y) {
1099 hotspot_x *= cursor->output->scale;
1100 hotspot_y *= cursor->output->scale;
1101
1102 if (surface && surface == cursor->surface) {
1103 // Only update the hotspot: surface hasn't changed
1104
1105 if (cursor->output->hardware_cursor != cursor) {
1106 output_cursor_damage_whole(cursor);
1107 }
1108 cursor->hotspot_x = hotspot_x;
1109 cursor->hotspot_y = hotspot_y;
1110 if (cursor->output->hardware_cursor != cursor) {
1111 output_cursor_damage_whole(cursor);
1112 } else {
1113 assert(cursor->output->impl->set_cursor);
1114 cursor->output->impl->set_cursor(cursor->output, NULL,
1115 1, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y, false);
1116 }
1117 return;
1118 }
1119
1120 output_cursor_reset(cursor);
1121
1122 cursor->surface = surface;
1123 cursor->hotspot_x = hotspot_x;
1124 cursor->hotspot_y = hotspot_y;
1125
1126 if (surface != NULL) {
1127 wl_signal_add(&surface->events.commit, &cursor->surface_commit);
1128 wl_signal_add(&surface->events.destroy, &cursor->surface_destroy);
1129
1130 cursor->visible = false;
1131 output_cursor_commit(cursor, false);
1132 } else {
1133 cursor->enabled = false;
1134 cursor->width = 0;
1135 cursor->height = 0;
1136
1137 if (cursor->output->hardware_cursor == cursor) {
1138 assert(cursor->output->impl->set_cursor);
1139 cursor->output->impl->set_cursor(cursor->output, NULL, 1,
1140 WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
1141 }
1142 }
1143 }
1144
wlr_output_cursor_move(struct wlr_output_cursor * cursor,double x,double y)1145 bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
1146 double x, double y) {
1147 if (cursor->x == x && cursor->y == y) {
1148 return true;
1149 }
1150
1151 if (cursor->output->hardware_cursor != cursor) {
1152 output_cursor_damage_whole(cursor);
1153 }
1154
1155 bool was_visible = cursor->visible;
1156 x *= cursor->output->scale;
1157 y *= cursor->output->scale;
1158 cursor->x = x;
1159 cursor->y = y;
1160 output_cursor_update_visible(cursor);
1161
1162 if (!was_visible && !cursor->visible) {
1163 // Cursor is still hidden, do nothing
1164 return true;
1165 }
1166
1167 if (cursor->output->hardware_cursor != cursor) {
1168 output_cursor_damage_whole(cursor);
1169 return true;
1170 }
1171
1172 assert(cursor->output->impl->move_cursor);
1173 return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y);
1174 }
1175
wlr_output_cursor_create(struct wlr_output * output)1176 struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) {
1177 struct wlr_output_cursor *cursor =
1178 calloc(1, sizeof(struct wlr_output_cursor));
1179 if (cursor == NULL) {
1180 return NULL;
1181 }
1182 cursor->output = output;
1183 wl_signal_init(&cursor->events.destroy);
1184 wl_list_init(&cursor->surface_commit.link);
1185 cursor->surface_commit.notify = output_cursor_handle_commit;
1186 wl_list_init(&cursor->surface_destroy.link);
1187 cursor->surface_destroy.notify = output_cursor_handle_destroy;
1188 wl_list_insert(&output->cursors, &cursor->link);
1189 cursor->visible = true; // default position is at (0, 0)
1190 return cursor;
1191 }
1192
wlr_output_cursor_destroy(struct wlr_output_cursor * cursor)1193 void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) {
1194 if (cursor == NULL) {
1195 return;
1196 }
1197 output_cursor_reset(cursor);
1198 wlr_signal_emit_safe(&cursor->events.destroy, cursor);
1199 if (cursor->output->hardware_cursor == cursor) {
1200 // If this cursor was the hardware cursor, disable it
1201 if (cursor->output->impl->set_cursor) {
1202 cursor->output->impl->set_cursor(cursor->output, NULL, 1,
1203 WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
1204 }
1205 cursor->output->hardware_cursor = NULL;
1206 }
1207 wlr_texture_destroy(cursor->texture);
1208 wl_list_remove(&cursor->link);
1209 free(cursor);
1210 }
1211
1212
wlr_output_transform_invert(enum wl_output_transform tr)1213 enum wl_output_transform wlr_output_transform_invert(
1214 enum wl_output_transform tr) {
1215 if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
1216 tr ^= WL_OUTPUT_TRANSFORM_180;
1217 }
1218 return tr;
1219 }
1220
wlr_output_transform_compose(enum wl_output_transform tr_a,enum wl_output_transform tr_b)1221 enum wl_output_transform wlr_output_transform_compose(
1222 enum wl_output_transform tr_a, enum wl_output_transform tr_b) {
1223 uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED;
1224 uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180;
1225 uint32_t rotated;
1226 if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) {
1227 // When a rotation of k degrees is followed by a flip, the
1228 // equivalent transform is a flip followed by a rotation of
1229 // -k degrees.
1230 rotated = (tr_b - tr_a) & rotation_mask;
1231 } else {
1232 rotated = (tr_a + tr_b) & rotation_mask;
1233 }
1234 return flipped | rotated;
1235 }
1236