diff options
author | Ivan Molodetskikh <yalterz@gmail.com> | 2019-09-25 13:58:27 +0300 |
---|---|---|
committer | Simon Ser <contact@emersion.fr> | 2019-11-17 20:18:42 +0100 |
commit | 022df2542baa057b1965a7c7ee9c32e738f637d2 (patch) | |
tree | e650befe8f210c0fc44eb8fadf3b125c70a1b01f | |
parent | Add -Wno-missing-braces (diff) | |
download | sway-022df2542baa057b1965a7c7ee9c32e738f637d2.tar.gz sway-022df2542baa057b1965a7c7ee9c32e738f637d2.tar.zst sway-022df2542baa057b1965a7c7ee9c32e738f637d2.zip |
output: add max_render_time
-rw-r--r-- | include/sway/commands.h | 1 | ||||
-rw-r--r-- | include/sway/config.h | 1 | ||||
-rw-r--r-- | include/sway/output.h | 7 | ||||
-rw-r--r-- | sway/commands/output.c | 1 | ||||
-rw-r--r-- | sway/commands/output/max_render_time.c | 28 | ||||
-rw-r--r-- | sway/config/output.c | 33 | ||||
-rw-r--r-- | sway/desktop/output.c | 91 | ||||
-rw-r--r-- | sway/meson.build | 1 | ||||
-rw-r--r-- | sway/sway-output.5.scd | 24 | ||||
-rw-r--r-- | sway/tree/output.c | 1 |
10 files changed, 166 insertions, 22 deletions
diff --git a/include/sway/commands.h b/include/sway/commands.h index d994ae9b..5d468a42 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h | |||
@@ -271,6 +271,7 @@ sway_cmd output_cmd_background; | |||
271 | sway_cmd output_cmd_disable; | 271 | sway_cmd output_cmd_disable; |
272 | sway_cmd output_cmd_dpms; | 272 | sway_cmd output_cmd_dpms; |
273 | sway_cmd output_cmd_enable; | 273 | sway_cmd output_cmd_enable; |
274 | sway_cmd output_cmd_max_render_time; | ||
274 | sway_cmd output_cmd_mode; | 275 | sway_cmd output_cmd_mode; |
275 | sway_cmd output_cmd_position; | 276 | sway_cmd output_cmd_position; |
276 | sway_cmd output_cmd_scale; | 277 | sway_cmd output_cmd_scale; |
diff --git a/include/sway/config.h b/include/sway/config.h index 6892c946..457e0a98 100644 --- a/include/sway/config.h +++ b/include/sway/config.h | |||
@@ -212,6 +212,7 @@ struct output_config { | |||
212 | float scale; | 212 | float scale; |
213 | int32_t transform; | 213 | int32_t transform; |
214 | enum wl_output_subpixel subpixel; | 214 | enum wl_output_subpixel subpixel; |
215 | int max_render_time; // In milliseconds | ||
215 | 216 | ||
216 | char *background; | 217 | char *background; |
217 | char *background_option; | 218 | char *background_option; |
diff --git a/include/sway/output.h b/include/sway/output.h index 7d7057e1..741f5b5e 100644 --- a/include/sway/output.h +++ b/include/sway/output.h | |||
@@ -51,6 +51,11 @@ struct sway_output { | |||
51 | struct { | 51 | struct { |
52 | struct wl_signal destroy; | 52 | struct wl_signal destroy; |
53 | } events; | 53 | } events; |
54 | |||
55 | struct timespec last_presentation; | ||
56 | uint32_t refresh_nsec; | ||
57 | int max_render_time; // In milliseconds | ||
58 | struct wl_event_source *repaint_timer; | ||
54 | }; | 59 | }; |
55 | 60 | ||
56 | struct sway_output *output_create(struct wlr_output *wlr_output); | 61 | struct sway_output *output_create(struct wlr_output *wlr_output); |
@@ -71,6 +76,8 @@ typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, | |||
71 | struct wlr_surface *surface, struct wlr_box *box, float rotation, | 76 | struct wlr_surface *surface, struct wlr_box *box, float rotation, |
72 | void *user_data); | 77 | void *user_data); |
73 | 78 | ||
79 | int output_repaint_timer_handler(void *data); | ||
80 | |||
74 | void output_damage_whole(struct sway_output *output); | 81 | void output_damage_whole(struct sway_output *output); |
75 | 82 | ||
76 | void output_damage_surface(struct sway_output *output, double ox, double oy, | 83 | void output_damage_surface(struct sway_output *output, double ox, double oy, |
diff --git a/sway/commands/output.c b/sway/commands/output.c index 59bc598c..db2acb50 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c | |||
@@ -12,6 +12,7 @@ static struct cmd_handler output_handlers[] = { | |||
12 | { "disable", output_cmd_disable }, | 12 | { "disable", output_cmd_disable }, |
13 | { "dpms", output_cmd_dpms }, | 13 | { "dpms", output_cmd_dpms }, |
14 | { "enable", output_cmd_enable }, | 14 | { "enable", output_cmd_enable }, |
15 | { "max_render_time", output_cmd_max_render_time }, | ||
15 | { "mode", output_cmd_mode }, | 16 | { "mode", output_cmd_mode }, |
16 | { "pos", output_cmd_position }, | 17 | { "pos", output_cmd_position }, |
17 | { "position", output_cmd_position }, | 18 | { "position", output_cmd_position }, |
diff --git a/sway/commands/output/max_render_time.c b/sway/commands/output/max_render_time.c new file mode 100644 index 00000000..2d3cebe3 --- /dev/null +++ b/sway/commands/output/max_render_time.c | |||
@@ -0,0 +1,28 @@ | |||
1 | #include <strings.h> | ||
2 | #include "sway/commands.h" | ||
3 | #include "sway/config.h" | ||
4 | |||
5 | struct cmd_results *output_cmd_max_render_time(int argc, char **argv) { | ||
6 | if (!config->handler_context.output_config) { | ||
7 | return cmd_results_new(CMD_FAILURE, "Missing output config"); | ||
8 | } | ||
9 | if (!argc) { | ||
10 | return cmd_results_new(CMD_INVALID, "Missing max render time argument."); | ||
11 | } | ||
12 | |||
13 | int max_render_time; | ||
14 | if (!strcmp(*argv, "off")) { | ||
15 | max_render_time = 0; | ||
16 | } else { | ||
17 | char *end; | ||
18 | max_render_time = strtol(*argv, &end, 10); | ||
19 | if (*end || max_render_time <= 0) { | ||
20 | return cmd_results_new(CMD_INVALID, "Invalid max render time."); | ||
21 | } | ||
22 | } | ||
23 | config->handler_context.output_config->max_render_time = max_render_time; | ||
24 | |||
25 | config->handler_context.leftovers.argc = argc - 1; | ||
26 | config->handler_context.leftovers.argv = argv + 1; | ||
27 | return NULL; | ||
28 | } | ||
diff --git a/sway/config/output.c b/sway/config/output.c index 25956fac..1d5f81da 100644 --- a/sway/config/output.c +++ b/sway/config/output.c | |||
@@ -47,6 +47,7 @@ struct output_config *new_output_config(const char *name) { | |||
47 | oc->scale = -1; | 47 | oc->scale = -1; |
48 | oc->transform = -1; | 48 | oc->transform = -1; |
49 | oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; | 49 | oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; |
50 | oc->max_render_time = -1; | ||
50 | return oc; | 51 | return oc; |
51 | } | 52 | } |
52 | 53 | ||
@@ -81,6 +82,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { | |||
81 | if (src->transform != -1) { | 82 | if (src->transform != -1) { |
82 | dst->transform = src->transform; | 83 | dst->transform = src->transform; |
83 | } | 84 | } |
85 | if (src->max_render_time != -1) { | ||
86 | dst->max_render_time = src->max_render_time; | ||
87 | } | ||
84 | if (src->background) { | 88 | if (src->background) { |
85 | free(dst->background); | 89 | free(dst->background); |
86 | dst->background = strdup(src->background); | 90 | dst->background = strdup(src->background); |
@@ -153,11 +157,12 @@ static void merge_id_on_name(struct output_config *oc) { | |||
153 | list_add(config->output_configs, ion_oc); | 157 | list_add(config->output_configs, ion_oc); |
154 | sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" | 158 | sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" |
155 | " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " | 159 | " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " |
156 | "transform %d) (bg %s %s) (dpms %d)", ion_oc->name, | 160 | "transform %d) (bg %s %s) (dpms %d) (max render time: %d)", |
157 | ion_oc->enabled, ion_oc->width, ion_oc->height, | 161 | ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height, |
158 | ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, | 162 | ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, |
159 | ion_oc->transform, ion_oc->background, | 163 | ion_oc->transform, ion_oc->background, |
160 | ion_oc->background_option, ion_oc->dpms_state); | 164 | ion_oc->background_option, ion_oc->dpms_state, |
165 | ion_oc->max_render_time); | ||
161 | } | 166 | } |
162 | } | 167 | } |
163 | free(id_on_name); | 168 | free(id_on_name); |
@@ -197,10 +202,12 @@ struct output_config *store_output_config(struct output_config *oc) { | |||
197 | } | 202 | } |
198 | 203 | ||
199 | sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " | 204 | sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " |
200 | "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d)", | 205 | "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d) " |
206 | "(max render time: %d)", | ||
201 | oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, | 207 | oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, |
202 | oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), | 208 | oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), |
203 | oc->transform, oc->background, oc->background_option, oc->dpms_state); | 209 | oc->transform, oc->background, oc->background_option, oc->dpms_state, |
210 | oc->max_render_time); | ||
204 | 211 | ||
205 | return oc; | 212 | return oc; |
206 | } | 213 | } |
@@ -325,6 +332,12 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
325 | wlr_output_enable(wlr_output, false); | 332 | wlr_output_enable(wlr_output, false); |
326 | } | 333 | } |
327 | 334 | ||
335 | if (oc && oc->max_render_time >= 0) { | ||
336 | sway_log(SWAY_DEBUG, "Set %s max render time to %d", | ||
337 | oc->name, oc->max_render_time); | ||
338 | output->max_render_time = oc->max_render_time; | ||
339 | } | ||
340 | |||
328 | return true; | 341 | return true; |
329 | } | 342 | } |
330 | 343 | ||
@@ -343,6 +356,7 @@ static void default_output_config(struct output_config *oc, | |||
343 | oc->subpixel = output->detected_subpixel; | 356 | oc->subpixel = output->detected_subpixel; |
344 | oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; | 357 | oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; |
345 | oc->dpms_state = DPMS_ON; | 358 | oc->dpms_state = DPMS_ON; |
359 | oc->max_render_time = 0; | ||
346 | } | 360 | } |
347 | 361 | ||
348 | static struct output_config *get_output_config(char *identifier, | 362 | static struct output_config *get_output_config(char *identifier, |
@@ -396,10 +410,11 @@ static struct output_config *get_output_config(char *identifier, | |||
396 | 410 | ||
397 | sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" | 411 | sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" |
398 | " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" | 412 | " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" |
399 | " (dpms %d)", result->name, result->enabled, result->width, | 413 | " (dpms %d) (max render time: %d)", result->name, result->enabled, |
400 | result->height, result->refresh_rate, result->x, result->y, | 414 | result->width, result->height, result->refresh_rate, |
401 | result->scale, result->transform, result->background, | 415 | result->x, result->y, result->scale, result->transform, |
402 | result->background_option, result->dpms_state); | 416 | result->background, result->background_option, result->dpms_state, |
417 | result->max_render_time); | ||
403 | } else if (oc_name) { | 418 | } else if (oc_name) { |
404 | // No identifier config, just return a copy of the name config | 419 | // No identifier config, just return a copy of the name config |
405 | free(result->name); | 420 | free(result->name); |
diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 05b5cd44..0f715c19 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c | |||
@@ -480,19 +480,13 @@ static bool scan_out_fullscreen_view(struct sway_output *output, | |||
480 | return wlr_output_commit(wlr_output); | 480 | return wlr_output_commit(wlr_output); |
481 | } | 481 | } |
482 | 482 | ||
483 | static void damage_handle_frame(struct wl_listener *listener, void *data) { | 483 | int output_repaint_timer_handler(void *data) { |
484 | struct sway_output *output = | 484 | struct sway_output *output = data; |
485 | wl_container_of(listener, output, damage_frame); | 485 | output->wlr_output->block_idle_frame = false; |
486 | if (!output->enabled || !output->wlr_output->enabled) { | ||
487 | return; | ||
488 | } | ||
489 | |||
490 | struct timespec now; | ||
491 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
492 | 486 | ||
493 | struct sway_workspace *workspace = output->current.active_workspace; | 487 | struct sway_workspace *workspace = output->current.active_workspace; |
494 | if (workspace == NULL) { | 488 | if (workspace == NULL) { |
495 | return; | 489 | return 0; |
496 | } | 490 | } |
497 | 491 | ||
498 | struct sway_container *fullscreen_con = root->fullscreen_global; | 492 | struct sway_container *fullscreen_con = root->fullscreen_global; |
@@ -515,7 +509,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) { | |||
515 | last_scanned_out = scanned_out; | 509 | last_scanned_out = scanned_out; |
516 | 510 | ||
517 | if (scanned_out) { | 511 | if (scanned_out) { |
518 | goto frame_done; | 512 | return 0; |
519 | } | 513 | } |
520 | } | 514 | } |
521 | 515 | ||
@@ -524,17 +518,82 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) { | |||
524 | pixman_region32_init(&damage); | 518 | pixman_region32_init(&damage); |
525 | if (!wlr_output_damage_attach_render(output->damage, | 519 | if (!wlr_output_damage_attach_render(output->damage, |
526 | &needs_frame, &damage)) { | 520 | &needs_frame, &damage)) { |
527 | return; | 521 | return 0; |
528 | } | 522 | } |
529 | 523 | ||
530 | if (needs_frame) { | 524 | if (needs_frame) { |
525 | struct timespec now; | ||
526 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
527 | |||
531 | output_render(output, &now, &damage); | 528 | output_render(output, &now, &damage); |
532 | } | 529 | } |
533 | 530 | ||
534 | pixman_region32_fini(&damage); | 531 | pixman_region32_fini(&damage); |
535 | 532 | ||
536 | frame_done: | 533 | return 0; |
534 | } | ||
535 | |||
536 | static void damage_handle_frame(struct wl_listener *listener, void *data) { | ||
537 | struct sway_output *output = | ||
538 | wl_container_of(listener, output, damage_frame); | ||
539 | if (!output->enabled || !output->wlr_output->enabled) { | ||
540 | return; | ||
541 | } | ||
542 | |||
543 | // Compute predicted milliseconds until the next refresh. It's used for | ||
544 | // delaying both output rendering and surface frame callbacks. | ||
545 | int msec_until_refresh = 0; | ||
546 | |||
547 | if (output->max_render_time != 0) { | ||
548 | struct timespec now; | ||
549 | clockid_t presentation_clock | ||
550 | = wlr_backend_get_presentation_clock(server.backend); | ||
551 | clock_gettime(presentation_clock, &now); | ||
552 | |||
553 | const long NSEC_IN_SECONDS = 1000000000; | ||
554 | struct timespec predicted_refresh = output->last_presentation; | ||
555 | predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS; | ||
556 | predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS; | ||
557 | if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) { | ||
558 | predicted_refresh.tv_sec += 1; | ||
559 | predicted_refresh.tv_nsec -= NSEC_IN_SECONDS; | ||
560 | } | ||
561 | |||
562 | // If the predicted refresh time is before the current time then | ||
563 | // there's no point in delaying. | ||
564 | // | ||
565 | // We only check tv_sec because if the predicted refresh time is less | ||
566 | // than a second before the current time, then msec_until_refresh will | ||
567 | // end up slightly below zero, which will effectively disable the delay | ||
568 | // without potential disasterous negative overflows that could occur if | ||
569 | // tv_sec was not checked. | ||
570 | if (predicted_refresh.tv_sec >= now.tv_sec) { | ||
571 | long nsec_until_refresh | ||
572 | = (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS | ||
573 | + (predicted_refresh.tv_nsec - now.tv_nsec); | ||
574 | |||
575 | // We want msec_until_refresh to be conservative, that is, floored. | ||
576 | // If we have 7.9 msec until refresh, we better compute the delay | ||
577 | // as if we had only 7 msec, so that we don't accidentally delay | ||
578 | // more than necessary and miss a frame. | ||
579 | msec_until_refresh = nsec_until_refresh / 1000000; | ||
580 | } | ||
581 | } | ||
582 | |||
583 | int delay = msec_until_refresh - output->max_render_time; | ||
584 | |||
585 | // If the delay is less than 1 millisecond (which is the least we can wait) | ||
586 | // then just render right away. | ||
587 | if (delay < 1) { | ||
588 | output_repaint_timer_handler(output); | ||
589 | } else { | ||
590 | output->wlr_output->block_idle_frame = true; | ||
591 | wl_event_source_timer_update(output->repaint_timer, delay); | ||
592 | } | ||
593 | |||
537 | // Send frame done to all visible surfaces | 594 | // Send frame done to all visible surfaces |
595 | struct timespec now; | ||
596 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
538 | send_frame_done(output, &now); | 597 | send_frame_done(output, &now); |
539 | } | 598 | } |
540 | 599 | ||
@@ -768,6 +827,9 @@ static void handle_present(struct wl_listener *listener, void *data) { | |||
768 | return; | 827 | return; |
769 | } | 828 | } |
770 | 829 | ||
830 | output->last_presentation = *output_event->when; | ||
831 | output->refresh_nsec = output_event->refresh; | ||
832 | |||
771 | struct wlr_presentation_event event = { | 833 | struct wlr_presentation_event event = { |
772 | .output = output->wlr_output, | 834 | .output = output->wlr_output, |
773 | .tv_sec = (uint64_t)output_event->when->tv_sec, | 835 | .tv_sec = (uint64_t)output_event->when->tv_sec, |
@@ -806,6 +868,9 @@ void handle_new_output(struct wl_listener *listener, void *data) { | |||
806 | wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); | 868 | wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); |
807 | output->damage_destroy.notify = damage_handle_destroy; | 869 | output->damage_destroy.notify = damage_handle_destroy; |
808 | 870 | ||
871 | output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, | ||
872 | output_repaint_timer_handler, output); | ||
873 | |||
809 | struct output_config *oc = find_output_config(output); | 874 | struct output_config *oc = find_output_config(output); |
810 | if (!oc || oc->enabled) { | 875 | if (!oc || oc->enabled) { |
811 | output_enable(output, oc); | 876 | output_enable(output, oc); |
diff --git a/sway/meson.build b/sway/meson.build index 8fcf841c..76a31d73 100644 --- a/sway/meson.build +++ b/sway/meson.build | |||
@@ -175,6 +175,7 @@ sway_sources = files( | |||
175 | 'commands/output/disable.c', | 175 | 'commands/output/disable.c', |
176 | 'commands/output/dpms.c', | 176 | 'commands/output/dpms.c', |
177 | 'commands/output/enable.c', | 177 | 'commands/output/enable.c', |
178 | 'commands/output/max_render_time.c', | ||
178 | 'commands/output/mode.c', | 179 | 'commands/output/mode.c', |
179 | 'commands/output/position.c', | 180 | 'commands/output/position.c', |
180 | 'commands/output/scale.c', | 181 | 'commands/output/scale.c', |
diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index ae6ced24..01496f19 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd | |||
@@ -107,6 +107,30 @@ must be separated by one space. For example: | |||
107 | Enables or disables the specified output via DPMS. To turn an output off | 107 | Enables or disables the specified output via DPMS. To turn an output off |
108 | (ie. blank the screen but keep workspaces as-is), one can set DPMS to off. | 108 | (ie. blank the screen but keep workspaces as-is), one can set DPMS to off. |
109 | 109 | ||
110 | *output* <name> max_render_time off|<msec> | ||
111 | When set to a positive number of milliseconds, enables delaying output | ||
112 | rendering to reduce latency. The rendering is delayed in such a way as | ||
113 | to leave the specified number of milliseconds before the next | ||
114 | presentation for rendering. | ||
115 | |||
116 | The output rendering normally takes place immediately after a | ||
117 | presentation (vblank, buffer flip, etc.) and the frame callbacks are | ||
118 | sent to surfaces immediately after the rendering to give surfaces the | ||
119 | most time to draw their next frame. This results in slightly below 2 | ||
120 | frames of latency between the surface rendering and committing new | ||
121 | contents, and the contents being shown on screen, on average. When the | ||
122 | output rendering is delayed, the frame callbacks are sent immediately | ||
123 | after presentation, and the surfaces have a small timespan (1 / | ||
124 | (refresh rate) - max_render_time) to render and commit new contents to | ||
125 | be shown on the next presentation, resulting in below 1 frame of | ||
126 | latency. | ||
127 | |||
128 | To set this up for optimal latency: | ||
129 | . Launch some _full-screen_ application that renders continuously, like | ||
130 | *glxgears*. | ||
131 | . Start with *max_render_time 1*. Increment by *1* if you see frame | ||
132 | drops. | ||
133 | |||
110 | # SEE ALSO | 134 | # SEE ALSO |
111 | 135 | ||
112 | *sway*(5) *sway-input*(5) | 136 | *sway*(5) *sway-input*(5) |
diff --git a/sway/tree/output.c b/sway/tree/output.c index 07f04ee5..c4ec6eec 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c | |||
@@ -242,6 +242,7 @@ void output_destroy(struct sway_output *output) { | |||
242 | } | 242 | } |
243 | list_free(output->workspaces); | 243 | list_free(output->workspaces); |
244 | list_free(output->current.workspaces); | 244 | list_free(output->current.workspaces); |
245 | wl_event_source_remove(output->repaint_timer); | ||
245 | free(output); | 246 | free(output); |
246 | } | 247 | } |
247 | 248 | ||