diff options
-rw-r--r-- | include/sway/config.h | 29 | ||||
-rw-r--r-- | sway/commands/output.c | 4 | ||||
-rw-r--r-- | sway/commands/output/toggle.c | 2 | ||||
-rw-r--r-- | sway/commands/workspace.c | 4 | ||||
-rw-r--r-- | sway/config.c | 2 | ||||
-rw-r--r-- | sway/config/output.c | 772 | ||||
-rw-r--r-- | sway/desktop/layer_shell.c | 7 | ||||
-rw-r--r-- | sway/desktop/output.c | 124 | ||||
-rw-r--r-- | sway/input/keyboard.c | 1 | ||||
-rw-r--r-- | sway/input/seatop_down.c | 6 | ||||
-rw-r--r-- | sway/server.c | 9 | ||||
-rw-r--r-- | sway/sway-ipc.7.scd | 2 | ||||
-rw-r--r-- | sway/sway.5.scd | 6 | ||||
-rw-r--r-- | sway/sway_text_node.c | 8 | ||||
-rw-r--r-- | sway/tree/container.c | 4 |
15 files changed, 643 insertions, 337 deletions
diff --git a/include/sway/config.h b/include/sway/config.h index f9da1967..5ccc3e77 100644 --- a/include/sway/config.h +++ b/include/sway/config.h | |||
@@ -292,6 +292,14 @@ struct output_config { | |||
292 | }; | 292 | }; |
293 | 293 | ||
294 | /** | 294 | /** |
295 | * An output config pre-matched to an output | ||
296 | */ | ||
297 | struct matched_output_config { | ||
298 | struct sway_output *output; | ||
299 | struct output_config *config; | ||
300 | }; | ||
301 | |||
302 | /** | ||
295 | * Stores size of gaps for each side | 303 | * Stores size of gaps for each side |
296 | */ | 304 | */ |
297 | struct side_gaps { | 305 | struct side_gaps { |
@@ -680,20 +688,25 @@ const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filt | |||
680 | 688 | ||
681 | struct output_config *new_output_config(const char *name); | 689 | struct output_config *new_output_config(const char *name); |
682 | 690 | ||
683 | void merge_output_config(struct output_config *dst, struct output_config *src); | 691 | bool apply_output_configs(struct matched_output_config *configs, |
692 | size_t configs_len, bool test_only, bool degrade_to_off); | ||
684 | 693 | ||
685 | bool apply_output_config(struct output_config *oc, struct sway_output *output); | 694 | void apply_all_output_configs(void); |
686 | 695 | ||
687 | bool test_output_config(struct output_config *oc, struct sway_output *output); | 696 | void sort_output_configs_by_priority(struct matched_output_config *configs, |
697 | size_t configs_len); | ||
688 | 698 | ||
689 | struct output_config *store_output_config(struct output_config *oc); | 699 | /** |
700 | * store_output_config stores a new output config. An output may be matched by | ||
701 | * three different config types, in order of precedence: Identifier, name and | ||
702 | * wildcard. When storing a config type of lower precedence, assume that the | ||
703 | * user wants the config to take immediate effect by superseding (clearing) the | ||
704 | * same values from higher presedence configuration. | ||
705 | */ | ||
706 | void store_output_config(struct output_config *oc); | ||
690 | 707 | ||
691 | struct output_config *find_output_config(struct sway_output *output); | 708 | struct output_config *find_output_config(struct sway_output *output); |
692 | 709 | ||
693 | void apply_output_config_to_outputs(struct output_config *oc); | ||
694 | |||
695 | void reset_outputs(void); | ||
696 | |||
697 | void free_output_config(struct output_config *oc); | 710 | void free_output_config(struct output_config *oc); |
698 | 711 | ||
699 | bool spawn_swaybg(void); | 712 | bool spawn_swaybg(void); |
diff --git a/sway/commands/output.c b/sway/commands/output.c index 462dffd2..5e5d31b3 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c | |||
@@ -103,13 +103,13 @@ struct cmd_results *cmd_output(int argc, char **argv) { | |||
103 | 103 | ||
104 | bool background = output->background; | 104 | bool background = output->background; |
105 | 105 | ||
106 | output = store_output_config(output); | 106 | store_output_config(output); |
107 | 107 | ||
108 | // If reloading, the output configs will be applied after reading the | 108 | // If reloading, the output configs will be applied after reading the |
109 | // entire config and before the deferred commands so that an auto generated | 109 | // entire config and before the deferred commands so that an auto generated |
110 | // workspace name is not given to re-enabled outputs. | 110 | // workspace name is not given to re-enabled outputs. |
111 | if (!config->reloading && !config->validating) { | 111 | if (!config->reloading && !config->validating) { |
112 | apply_output_config_to_outputs(output); | 112 | apply_all_output_configs(); |
113 | if (background) { | 113 | if (background) { |
114 | if (!spawn_swaybg()) { | 114 | if (!spawn_swaybg()) { |
115 | return cmd_results_new(CMD_FAILURE, | 115 | return cmd_results_new(CMD_FAILURE, |
diff --git a/sway/commands/output/toggle.c b/sway/commands/output/toggle.c index 6342d526..c6b72845 100644 --- a/sway/commands/output/toggle.c +++ b/sway/commands/output/toggle.c | |||
@@ -29,7 +29,7 @@ struct cmd_results *output_cmd_toggle(int argc, char **argv) { | |||
29 | config->handler_context.output_config->enabled = 1; | 29 | config->handler_context.output_config->enabled = 1; |
30 | } | 30 | } |
31 | 31 | ||
32 | free(oc); | 32 | free_output_config(oc); |
33 | config->handler_context.leftovers.argc = argc; | 33 | config->handler_context.leftovers.argc = argc; |
34 | config->handler_context.leftovers.argv = argv; | 34 | config->handler_context.leftovers.argv = argv; |
35 | return NULL; | 35 | return NULL; |
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c index a14ebb20..37a201b4 100644 --- a/sway/commands/workspace.c +++ b/sway/commands/workspace.c | |||
@@ -157,6 +157,10 @@ struct cmd_results *cmd_workspace(int argc, char **argv) { | |||
157 | return cmd_results_new(CMD_FAILURE, | 157 | return cmd_results_new(CMD_FAILURE, |
158 | "Unable to allocate workspace output"); | 158 | "Unable to allocate workspace output"); |
159 | } | 159 | } |
160 | if (output_location + 1 < argc) { | ||
161 | list_free_items_and_destroy(wsc->outputs); | ||
162 | wsc->outputs = create_list(); | ||
163 | } | ||
160 | for (int i = output_location + 1; i < argc; ++i) { | 164 | for (int i = output_location + 1; i < argc; ++i) { |
161 | list_add(wsc->outputs, strdup(argv[i])); | 165 | list_add(wsc->outputs, strdup(argv[i])); |
162 | } | 166 | } |
diff --git a/sway/config.c b/sway/config.c index 72fc41e7..f9131e0f 100644 --- a/sway/config.c +++ b/sway/config.c | |||
@@ -532,7 +532,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { | |||
532 | } | 532 | } |
533 | sway_switch_retrigger_bindings_for_all(); | 533 | sway_switch_retrigger_bindings_for_all(); |
534 | 534 | ||
535 | reset_outputs(); | 535 | apply_all_output_configs(); |
536 | spawn_swaybg(); | 536 | spawn_swaybg(); |
537 | 537 | ||
538 | config->reloading = false; | 538 | config->reloading = false; |
diff --git a/sway/config/output.c b/sway/config/output.c index 1b2332e9..fb1956df 100644 --- a/sway/config/output.c +++ b/sway/config/output.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include <wlr/types/wlr_cursor.h> | 9 | #include <wlr/types/wlr_cursor.h> |
10 | #include <wlr/types/wlr_output_layout.h> | 10 | #include <wlr/types/wlr_output_layout.h> |
11 | #include <wlr/types/wlr_output.h> | 11 | #include <wlr/types/wlr_output.h> |
12 | #include <wlr/types/wlr_output_swapchain_manager.h> | ||
12 | #include "sway/config.h" | 13 | #include "sway/config.h" |
13 | #include "sway/input/cursor.h" | 14 | #include "sway/input/cursor.h" |
14 | #include "sway/output.h" | 15 | #include "sway/output.h" |
@@ -78,7 +79,72 @@ struct output_config *new_output_config(const char *name) { | |||
78 | return oc; | 79 | return oc; |
79 | } | 80 | } |
80 | 81 | ||
81 | void merge_output_config(struct output_config *dst, struct output_config *src) { | 82 | // supersede_output_config clears all fields in dst that were set in src |
83 | static void supersede_output_config(struct output_config *dst, struct output_config *src) { | ||
84 | if (src->enabled != -1) { | ||
85 | dst->enabled = -1; | ||
86 | } | ||
87 | if (src->width != -1) { | ||
88 | dst->width = -1; | ||
89 | } | ||
90 | if (src->height != -1) { | ||
91 | dst->height = -1; | ||
92 | } | ||
93 | if (src->x != -1) { | ||
94 | dst->x = -1; | ||
95 | } | ||
96 | if (src->y != -1) { | ||
97 | dst->y = -1; | ||
98 | } | ||
99 | if (src->scale != -1) { | ||
100 | dst->scale = -1; | ||
101 | } | ||
102 | if (src->scale_filter != SCALE_FILTER_DEFAULT) { | ||
103 | dst->scale_filter = SCALE_FILTER_DEFAULT; | ||
104 | } | ||
105 | if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { | ||
106 | dst->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; | ||
107 | } | ||
108 | if (src->refresh_rate != -1) { | ||
109 | dst->refresh_rate = -1; | ||
110 | } | ||
111 | if (src->custom_mode != -1) { | ||
112 | dst->custom_mode = -1; | ||
113 | } | ||
114 | if (src->drm_mode.type != (uint32_t) -1) { | ||
115 | dst->drm_mode.type = -1; | ||
116 | } | ||
117 | if (src->transform != -1) { | ||
118 | dst->transform = -1; | ||
119 | } | ||
120 | if (src->max_render_time != -1) { | ||
121 | dst->max_render_time = -1; | ||
122 | } | ||
123 | if (src->adaptive_sync != -1) { | ||
124 | dst->adaptive_sync = -1; | ||
125 | } | ||
126 | if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { | ||
127 | dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; | ||
128 | } | ||
129 | if (src->background) { | ||
130 | free(dst->background); | ||
131 | dst->background = NULL; | ||
132 | } | ||
133 | if (src->background_option) { | ||
134 | free(dst->background_option); | ||
135 | dst->background_option = NULL; | ||
136 | } | ||
137 | if (src->background_fallback) { | ||
138 | free(dst->background_fallback); | ||
139 | dst->background_fallback = NULL; | ||
140 | } | ||
141 | if (src->power != -1) { | ||
142 | dst->power = -1; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | // merge_output_config sets all fields in dst that were set in src | ||
147 | static void merge_output_config(struct output_config *dst, struct output_config *src) { | ||
82 | if (src->enabled != -1) { | 148 | if (src->enabled != -1) { |
83 | dst->enabled = src->enabled; | 149 | dst->enabled = src->enabled; |
84 | } | 150 | } |
@@ -141,94 +207,42 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { | |||
141 | } | 207 | } |
142 | } | 208 | } |
143 | 209 | ||
144 | static void merge_wildcard_on_all(struct output_config *wildcard) { | 210 | void store_output_config(struct output_config *oc) { |
145 | for (int i = 0; i < config->output_configs->length; i++) { | 211 | bool merged = false; |
146 | struct output_config *oc = config->output_configs->items[i]; | 212 | bool wildcard = strcmp(oc->name, "*") == 0; |
147 | if (strcmp(wildcard->name, oc->name) != 0) { | 213 | struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name); |
148 | sway_log(SWAY_DEBUG, "Merging output * config on %s", oc->name); | ||
149 | merge_output_config(oc, wildcard); | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
154 | static void merge_id_on_name(struct output_config *oc) { | ||
155 | struct sway_output *output = all_output_by_name_or_id(oc->name); | ||
156 | if (output == NULL) { | ||
157 | return; | ||
158 | } | ||
159 | 214 | ||
160 | const char *name = output->wlr_output->name; | ||
161 | char id[128]; | 215 | char id[128]; |
162 | output_get_identifier(id, sizeof(id), output); | 216 | if (output) { |
163 | 217 | output_get_identifier(id, sizeof(id), output); | |
164 | char *id_on_name = format_str("%s on %s", id, name); | ||
165 | if (!id_on_name) { | ||
166 | return; | ||
167 | } | 218 | } |
168 | 219 | ||
169 | int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); | 220 | for (int i = 0; i < config->output_configs->length; i++) { |
170 | if (i >= 0) { | 221 | struct output_config *old = config->output_configs->items[i]; |
171 | sway_log(SWAY_DEBUG, "Merging on top of existing id on name config"); | 222 | |
172 | merge_output_config(config->output_configs->items[i], oc); | 223 | // If the old config matches the new config's name, regardless of |
173 | } else { | 224 | // whether it was name or identifier, merge on top of the existing |
174 | // If both a name and identifier config, exist generate an id on name | 225 | // config. If the new config is a wildcard, this also merges on top of |
175 | int ni = list_seq_find(config->output_configs, output_name_cmp, name); | 226 | // old wildcard configs. |
176 | int ii = list_seq_find(config->output_configs, output_name_cmp, id); | 227 | if (strcmp(old->name, oc->name) == 0) { |
177 | if ((ni >= 0 && ii >= 0) || (ni >= 0 && strcmp(oc->name, id) == 0) | 228 | merge_output_config(old, oc); |
178 | || (ii >= 0 && strcmp(oc->name, name) == 0)) { | 229 | merged = true; |
179 | struct output_config *ion_oc = new_output_config(id_on_name); | 230 | continue; |
180 | if (ni >= 0) { | ||
181 | merge_output_config(ion_oc, config->output_configs->items[ni]); | ||
182 | } | ||
183 | if (ii >= 0) { | ||
184 | merge_output_config(ion_oc, config->output_configs->items[ii]); | ||
185 | } | ||
186 | merge_output_config(ion_oc, oc); | ||
187 | list_add(config->output_configs, ion_oc); | ||
188 | sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" | ||
189 | " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " | ||
190 | "transform %d) (bg %s %s) (power %d) (max render time: %d)", | ||
191 | ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height, | ||
192 | ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, | ||
193 | ion_oc->transform, ion_oc->background, | ||
194 | ion_oc->background_option, ion_oc->power, | ||
195 | ion_oc->max_render_time); | ||
196 | } | 231 | } |
197 | } | ||
198 | free(id_on_name); | ||
199 | } | ||
200 | 232 | ||
201 | struct output_config *store_output_config(struct output_config *oc) { | 233 | // If the new config is a wildcard config we supersede all non-wildcard |
202 | bool wildcard = strcmp(oc->name, "*") == 0; | 234 | // configs. Old wildcard configs have already been handled above. |
203 | if (wildcard) { | 235 | if (wildcard) { |
204 | merge_wildcard_on_all(oc); | 236 | supersede_output_config(old, oc); |
205 | } else { | 237 | continue; |
206 | merge_id_on_name(oc); | 238 | } |
207 | } | ||
208 | 239 | ||
209 | int i = list_seq_find(config->output_configs, output_name_cmp, oc->name); | 240 | // If the new config matches an output's name, and the old config |
210 | if (i >= 0) { | 241 | // matches on that output's identifier, supersede it. |
211 | sway_log(SWAY_DEBUG, "Merging on top of existing output config"); | 242 | if (output && strcmp(old->name, id) == 0 && |
212 | struct output_config *current = config->output_configs->items[i]; | 243 | strcmp(oc->name, output->wlr_output->name) == 0) { |
213 | merge_output_config(current, oc); | 244 | supersede_output_config(old, oc); |
214 | free_output_config(oc); | ||
215 | oc = current; | ||
216 | } else if (!wildcard) { | ||
217 | sway_log(SWAY_DEBUG, "Adding non-wildcard output config"); | ||
218 | i = list_seq_find(config->output_configs, output_name_cmp, "*"); | ||
219 | if (i >= 0) { | ||
220 | sway_log(SWAY_DEBUG, "Merging on top of output * config"); | ||
221 | struct output_config *current = new_output_config(oc->name); | ||
222 | merge_output_config(current, config->output_configs->items[i]); | ||
223 | merge_output_config(current, oc); | ||
224 | free_output_config(oc); | ||
225 | oc = current; | ||
226 | } | 245 | } |
227 | list_add(config->output_configs, oc); | ||
228 | } else { | ||
229 | // New wildcard config. Just add it | ||
230 | sway_log(SWAY_DEBUG, "Adding output * config"); | ||
231 | list_add(config->output_configs, oc); | ||
232 | } | 246 | } |
233 | 247 | ||
234 | sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " | 248 | sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " |
@@ -239,7 +253,13 @@ struct output_config *store_output_config(struct output_config *oc) { | |||
239 | oc->transform, oc->background, oc->background_option, oc->power, | 253 | oc->transform, oc->background, oc->background_option, oc->power, |
240 | oc->max_render_time); | 254 | oc->max_render_time); |
241 | 255 | ||
242 | return oc; | 256 | // If the configuration was not merged into an existing configuration, add |
257 | // it to the list. Otherwise we're done with it and can free it. | ||
258 | if (!merged) { | ||
259 | list_add(config->output_configs, oc); | ||
260 | } else { | ||
261 | free_output_config(oc); | ||
262 | } | ||
243 | } | 263 | } |
244 | 264 | ||
245 | static void set_mode(struct wlr_output *output, struct wlr_output_state *pending, | 265 | static void set_mode(struct wlr_output *output, struct wlr_output_state *pending, |
@@ -366,22 +386,18 @@ static int compute_default_scale(struct wlr_output *output, | |||
366 | return 2; | 386 | return 2; |
367 | } | 387 | } |
368 | 388 | ||
369 | /* Lists of formats to try, in order, when a specific render bit depth has | 389 | static bool render_format_is_10bit(uint32_t render_format) { |
370 | * been asked for. The second to last format in each list should always | 390 | return render_format == DRM_FORMAT_XRGB2101010 || |
371 | * be XRGB8888, as a reliable backup in case the others are not available; | 391 | render_format == DRM_FORMAT_XBGR2101010; |
372 | * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */ | 392 | } |
373 | static const uint32_t *bit_depth_preferences[] = { | 393 | |
374 | [RENDER_BIT_DEPTH_8] = (const uint32_t []){ | 394 | static bool render_format_is_bgr(uint32_t fmt) { |
375 | DRM_FORMAT_XRGB8888, | 395 | return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888; |
376 | DRM_FORMAT_INVALID, | 396 | } |
377 | }, | 397 | |
378 | [RENDER_BIT_DEPTH_10] = (const uint32_t []){ | 398 | static bool output_config_is_disabling(struct output_config *oc) { |
379 | DRM_FORMAT_XRGB2101010, | 399 | return oc && (!oc->enabled || oc->power == 0); |
380 | DRM_FORMAT_XBGR2101010, | 400 | } |
381 | DRM_FORMAT_XRGB8888, | ||
382 | DRM_FORMAT_INVALID, | ||
383 | }, | ||
384 | }; | ||
385 | 401 | ||
386 | static void queue_output_config(struct output_config *oc, | 402 | static void queue_output_config(struct output_config *oc, |
387 | struct sway_output *output, struct wlr_output_state *pending) { | 403 | struct sway_output *output, struct wlr_output_state *pending) { |
@@ -391,7 +407,7 @@ static void queue_output_config(struct output_config *oc, | |||
391 | 407 | ||
392 | struct wlr_output *wlr_output = output->wlr_output; | 408 | struct wlr_output *wlr_output = output->wlr_output; |
393 | 409 | ||
394 | if (oc && (!oc->enabled || oc->power == 0)) { | 410 | if (output_config_is_disabling(oc)) { |
395 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); | 411 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); |
396 | wlr_output_state_set_enabled(pending, false); | 412 | wlr_output_state_set_enabled(pending, false); |
397 | return; | 413 | return; |
@@ -414,22 +430,6 @@ static void queue_output_config(struct output_config *oc, | |||
414 | struct wlr_output_mode *preferred_mode = | 430 | struct wlr_output_mode *preferred_mode = |
415 | wlr_output_preferred_mode(wlr_output); | 431 | wlr_output_preferred_mode(wlr_output); |
416 | wlr_output_state_set_mode(pending, preferred_mode); | 432 | wlr_output_state_set_mode(pending, preferred_mode); |
417 | |||
418 | if (!wlr_output_test_state(wlr_output, pending)) { | ||
419 | sway_log(SWAY_DEBUG, "Preferred mode rejected, " | ||
420 | "falling back to another mode"); | ||
421 | struct wlr_output_mode *mode; | ||
422 | wl_list_for_each(mode, &wlr_output->modes, link) { | ||
423 | if (mode == preferred_mode) { | ||
424 | continue; | ||
425 | } | ||
426 | |||
427 | wlr_output_state_set_mode(pending, mode); | ||
428 | if (wlr_output_test_state(wlr_output, pending)) { | ||
429 | break; | ||
430 | } | ||
431 | } | ||
432 | } | ||
433 | } | 433 | } |
434 | 434 | ||
435 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { | 435 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { |
@@ -480,48 +480,27 @@ static void queue_output_config(struct output_config *oc, | |||
480 | sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, | 480 | sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, |
481 | oc->adaptive_sync); | 481 | oc->adaptive_sync); |
482 | wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); | 482 | wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); |
483 | if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) { | ||
484 | sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring"); | ||
485 | wlr_output_state_set_adaptive_sync_enabled(pending, false); | ||
486 | } | ||
487 | } | 483 | } |
488 | 484 | ||
489 | if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { | 485 | if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { |
490 | const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth]; | 486 | if (oc->render_bit_depth == RENDER_BIT_DEPTH_10 && |
491 | assert(fmts); | 487 | render_format_is_10bit(output->wlr_output->render_format)) { |
492 | 488 | // 10-bit was set successfully before, try to save some tests by reusing the format | |
493 | for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) { | 489 | wlr_output_state_set_render_format(pending, output->wlr_output->render_format); |
494 | wlr_output_state_set_render_format(pending, fmts[i]); | 490 | } else if (oc->render_bit_depth == RENDER_BIT_DEPTH_10) { |
495 | if (wlr_output_test_state(wlr_output, pending)) { | 491 | wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010); |
496 | break; | 492 | } else { |
497 | } | 493 | wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888); |
498 | |||
499 | sway_log(SWAY_DEBUG, "Preferred output format 0x%08x " | ||
500 | "failed to work, falling back to next in " | ||
501 | "list, 0x%08x", fmts[i], fmts[i + 1]); | ||
502 | } | 494 | } |
503 | } | 495 | } |
504 | } | 496 | } |
505 | 497 | ||
506 | bool apply_output_config(struct output_config *oc, struct sway_output *output) { | 498 | static bool finalize_output_config(struct output_config *oc, struct sway_output *output) { |
507 | if (output == root->fallback_output) { | 499 | if (output == root->fallback_output) { |
508 | return false; | 500 | return false; |
509 | } | 501 | } |
510 | 502 | ||
511 | struct wlr_output *wlr_output = output->wlr_output; | 503 | struct wlr_output *wlr_output = output->wlr_output; |
512 | |||
513 | struct wlr_output_state pending = {0}; | ||
514 | queue_output_config(oc, output, &pending); | ||
515 | |||
516 | sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name); | ||
517 | if (!wlr_output_commit_state(wlr_output, &pending)) { | ||
518 | // Failed to commit output changes, maybe the output is missing a CRTC. | ||
519 | // Leave the output disabled for now and try again when the output gets | ||
520 | // the mode we asked for. | ||
521 | sway_log(SWAY_ERROR, "Failed to commit output %s", wlr_output->name); | ||
522 | return false; | ||
523 | } | ||
524 | |||
525 | if (oc && !oc->enabled) { | 504 | if (oc && !oc->enabled) { |
526 | sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); | 505 | sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); |
527 | if (output->enabled) { | 506 | if (output->enabled) { |
@@ -577,25 +556,9 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
577 | output->max_render_time = oc->max_render_time; | 556 | output->max_render_time = oc->max_render_time; |
578 | } | 557 | } |
579 | 558 | ||
580 | // Reconfigure all devices, since input config may have been applied before | ||
581 | // this output came online, and some config items (like map_to_output) are | ||
582 | // dependent on an output being present. | ||
583 | input_manager_configure_all_input_mappings(); | ||
584 | // Reconfigure the cursor images, since the scale may have changed. | ||
585 | input_manager_configure_xcursor(); | ||
586 | return true; | 559 | return true; |
587 | } | 560 | } |
588 | 561 | ||
589 | bool test_output_config(struct output_config *oc, struct sway_output *output) { | ||
590 | if (output == root->fallback_output) { | ||
591 | return false; | ||
592 | } | ||
593 | |||
594 | struct wlr_output_state pending = {0}; | ||
595 | queue_output_config(oc, output, &pending); | ||
596 | return wlr_output_test_state(output->wlr_output, &pending); | ||
597 | } | ||
598 | |||
599 | static void default_output_config(struct output_config *oc, | 562 | static void default_output_config(struct output_config *oc, |
600 | struct wlr_output *wlr_output) { | 563 | struct wlr_output *wlr_output) { |
601 | oc->enabled = 1; | 564 | oc->enabled = 1; |
@@ -615,140 +578,415 @@ static void default_output_config(struct output_config *oc, | |||
615 | oc->max_render_time = 0; | 578 | oc->max_render_time = 0; |
616 | } | 579 | } |
617 | 580 | ||
618 | static struct output_config *get_output_config(char *identifier, | 581 | // find_output_config returns a merged output_config containing all stored |
619 | struct sway_output *sway_output) { | 582 | // configuration that applies to the specified output. |
583 | struct output_config *find_output_config(struct sway_output *sway_output) { | ||
620 | const char *name = sway_output->wlr_output->name; | 584 | const char *name = sway_output->wlr_output->name; |
585 | struct output_config *oc = NULL; | ||
621 | 586 | ||
622 | struct output_config *oc_id_on_name = NULL; | 587 | struct output_config *result = new_output_config(name); |
623 | struct output_config *oc_name = NULL; | 588 | if (config->reloading) { |
624 | struct output_config *oc_id = NULL; | 589 | default_output_config(result, sway_output->wlr_output); |
590 | } | ||
625 | 591 | ||
626 | char *id_on_name = format_str("%s on %s", identifier, name); | 592 | char id[128]; |
627 | int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); | 593 | output_get_identifier(id, sizeof(id), sway_output); |
628 | if (i >= 0) { | 594 | |
629 | oc_id_on_name = config->output_configs->items[i]; | 595 | int i; |
630 | } else { | 596 | bool match = false; |
631 | i = list_seq_find(config->output_configs, output_name_cmp, name); | 597 | if ((i = list_seq_find(config->output_configs, output_name_cmp, "*")) >= 0) { |
632 | if (i >= 0) { | 598 | match = true; |
633 | oc_name = config->output_configs->items[i]; | 599 | oc = config->output_configs->items[i]; |
600 | merge_output_config(result, oc); | ||
601 | } | ||
602 | if ((i = list_seq_find(config->output_configs, output_name_cmp, name)) >= 0) { | ||
603 | match = true; | ||
604 | oc = config->output_configs->items[i]; | ||
605 | merge_output_config(result, oc); | ||
606 | } | ||
607 | if ((i = list_seq_find(config->output_configs, output_name_cmp, id)) >= 0) { | ||
608 | match = true; | ||
609 | oc = config->output_configs->items[i]; | ||
610 | merge_output_config(result, oc); | ||
611 | } | ||
612 | |||
613 | if (!match && !config->reloading) { | ||
614 | // No name, identifier, or wildcard config. Since we are not | ||
615 | // reloading with defaults, the output config will be empty, so | ||
616 | // just return NULL | ||
617 | free_output_config(result); | ||
618 | return NULL; | ||
619 | } | ||
620 | |||
621 | return result; | ||
622 | } | ||
623 | |||
624 | static bool config_has_auto_mode(struct output_config *oc) { | ||
625 | if (!oc) { | ||
626 | return true; | ||
627 | } | ||
628 | if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) { | ||
629 | return true; | ||
630 | } else if (oc->width > 0 && oc->height > 0) { | ||
631 | return true; | ||
632 | } | ||
633 | return false; | ||
634 | } | ||
635 | |||
636 | struct search_context { | ||
637 | struct wlr_output_swapchain_manager *swapchain_mgr; | ||
638 | struct wlr_backend_output_state *states; | ||
639 | struct matched_output_config *configs; | ||
640 | size_t configs_len; | ||
641 | bool degrade_to_off; | ||
642 | }; | ||
643 | |||
644 | static void dump_output_state(struct wlr_output *wlr_output, struct wlr_output_state *state) { | ||
645 | sway_log(SWAY_DEBUG, "Output state for %s", wlr_output->name); | ||
646 | if (state->committed & WLR_OUTPUT_STATE_ENABLED) { | ||
647 | sway_log(SWAY_DEBUG, " enabled: %s", state->enabled ? "yes" : "no"); | ||
648 | } | ||
649 | if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { | ||
650 | sway_log(SWAY_DEBUG, " render_format: %d", state->render_format); | ||
651 | } | ||
652 | if (state->committed & WLR_OUTPUT_STATE_MODE) { | ||
653 | if (state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { | ||
654 | sway_log(SWAY_DEBUG, " custom mode: %dx%d@%dmHz", | ||
655 | state->custom_mode.width, state->custom_mode.height, state->custom_mode.refresh); | ||
656 | } else { | ||
657 | sway_log(SWAY_DEBUG, " mode: %dx%d@%dmHz%s", | ||
658 | state->mode->width, state->mode->height, state->mode->refresh, | ||
659 | state->mode->preferred ? " (preferred)" : ""); | ||
634 | } | 660 | } |
661 | } | ||
662 | if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { | ||
663 | sway_log(SWAY_DEBUG, " adaptive_sync: %s", | ||
664 | state->adaptive_sync_enabled ? "enabled": "disabled"); | ||
665 | } | ||
666 | } | ||
667 | |||
668 | static bool search_valid_config(struct search_context *ctx, size_t output_idx); | ||
669 | |||
670 | static void reset_output_state(struct wlr_output_state *state) { | ||
671 | wlr_output_state_finish(state); | ||
672 | wlr_output_state_init(state); | ||
673 | state->committed = 0; | ||
674 | } | ||
675 | |||
676 | static void clear_later_output_states(struct wlr_backend_output_state *states, | ||
677 | size_t configs_len, size_t output_idx) { | ||
635 | 678 | ||
636 | i = list_seq_find(config->output_configs, output_name_cmp, identifier); | 679 | // Clear and disable all output states after this one to avoid conflict |
637 | if (i >= 0) { | 680 | // with previous tests. |
638 | oc_id = config->output_configs->items[i]; | 681 | for (size_t idx = output_idx+1; idx < configs_len; idx++) { |
682 | struct wlr_backend_output_state *backend_state = &states[idx]; | ||
683 | struct wlr_output_state *state = &backend_state->base; | ||
684 | |||
685 | reset_output_state(state); | ||
686 | wlr_output_state_set_enabled(state, false); | ||
687 | } | ||
688 | } | ||
689 | |||
690 | static bool search_finish(struct search_context *ctx, size_t output_idx) { | ||
691 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
692 | struct wlr_output_state *state = &backend_state->base; | ||
693 | struct wlr_output *wlr_output = backend_state->output; | ||
694 | |||
695 | clear_later_output_states(ctx->states, ctx->configs_len, output_idx); | ||
696 | dump_output_state(wlr_output, state); | ||
697 | return wlr_output_swapchain_manager_prepare(ctx->swapchain_mgr, ctx->states, ctx->configs_len) && | ||
698 | search_valid_config(ctx, output_idx+1); | ||
699 | } | ||
700 | |||
701 | static bool search_adaptive_sync(struct search_context *ctx, size_t output_idx) { | ||
702 | struct matched_output_config *cfg = &ctx->configs[output_idx]; | ||
703 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
704 | struct wlr_output_state *state = &backend_state->base; | ||
705 | |||
706 | if (cfg->config && cfg->config->adaptive_sync == 1) { | ||
707 | wlr_output_state_set_adaptive_sync_enabled(state, true); | ||
708 | if (search_finish(ctx, output_idx)) { | ||
709 | return true; | ||
710 | } | ||
711 | } | ||
712 | if (!cfg->config || cfg->config->adaptive_sync != -1) { | ||
713 | wlr_output_state_set_adaptive_sync_enabled(state, false); | ||
714 | if (search_finish(ctx, output_idx)) { | ||
715 | return true; | ||
639 | } | 716 | } |
640 | } | 717 | } |
718 | // If adaptive sync has not been set, or fallback in case we are on a | ||
719 | // backend that cannot disable adaptive sync such as the wayland backend. | ||
720 | state->committed &= ~WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; | ||
721 | return search_finish(ctx, output_idx); | ||
722 | } | ||
641 | 723 | ||
642 | struct output_config *result = new_output_config("temp"); | 724 | static bool search_mode(struct search_context *ctx, size_t output_idx) { |
643 | if (config->reloading) { | 725 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
644 | default_output_config(result, sway_output->wlr_output); | 726 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; |
727 | struct wlr_output_state *state = &backend_state->base; | ||
728 | struct wlr_output *wlr_output = backend_state->output; | ||
729 | |||
730 | if (!config_has_auto_mode(cfg->config)) { | ||
731 | return search_adaptive_sync(ctx, output_idx); | ||
645 | } | 732 | } |
646 | if (oc_id_on_name) { | 733 | |
647 | // Already have an identifier on name config, use that | 734 | struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); |
648 | free(result->name); | 735 | if (preferred_mode) { |
649 | result->name = strdup(id_on_name); | 736 | wlr_output_state_set_mode(state, preferred_mode); |
650 | merge_output_config(result, oc_id_on_name); | 737 | if (search_adaptive_sync(ctx, output_idx)) { |
651 | } else if (oc_name && oc_id) { | 738 | return true; |
652 | // Generate a config named `<identifier> on <name>` which contains a | ||
653 | // merged copy of the identifier on name. This will make sure that both | ||
654 | // identifier and name configs are respected, with identifier getting | ||
655 | // priority | ||
656 | struct output_config *temp = new_output_config(id_on_name); | ||
657 | merge_output_config(temp, oc_name); | ||
658 | merge_output_config(temp, oc_id); | ||
659 | list_add(config->output_configs, temp); | ||
660 | |||
661 | free(result->name); | ||
662 | result->name = strdup(id_on_name); | ||
663 | merge_output_config(result, temp); | ||
664 | |||
665 | sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" | ||
666 | " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" | ||
667 | " (power %d) (max render time: %d)", result->name, result->enabled, | ||
668 | result->width, result->height, result->refresh_rate, | ||
669 | result->x, result->y, result->scale, result->transform, | ||
670 | result->background, result->background_option, result->power, | ||
671 | result->max_render_time); | ||
672 | } else if (oc_name) { | ||
673 | // No identifier config, just return a copy of the name config | ||
674 | free(result->name); | ||
675 | result->name = strdup(name); | ||
676 | merge_output_config(result, oc_name); | ||
677 | } else if (oc_id) { | ||
678 | // No name config, just return a copy of the identifier config | ||
679 | free(result->name); | ||
680 | result->name = strdup(identifier); | ||
681 | merge_output_config(result, oc_id); | ||
682 | } else { | ||
683 | i = list_seq_find(config->output_configs, output_name_cmp, "*"); | ||
684 | if (i >= 0) { | ||
685 | // No name or identifier config, but there is a wildcard config | ||
686 | free(result->name); | ||
687 | result->name = strdup("*"); | ||
688 | merge_output_config(result, config->output_configs->items[i]); | ||
689 | } else if (!config->reloading) { | ||
690 | // No name, identifier, or wildcard config. Since we are not | ||
691 | // reloading with defaults, the output config will be empty, so | ||
692 | // just return NULL | ||
693 | free_output_config(result); | ||
694 | result = NULL; | ||
695 | } | 739 | } |
696 | } | 740 | } |
697 | 741 | ||
698 | free(id_on_name); | 742 | if (wl_list_empty(&wlr_output->modes)) { |
699 | return result; | 743 | state->committed &= ~WLR_OUTPUT_STATE_MODE; |
744 | return search_adaptive_sync(ctx, output_idx); | ||
745 | } | ||
746 | |||
747 | struct wlr_output_mode *mode; | ||
748 | wl_list_for_each(mode, &backend_state->output->modes, link) { | ||
749 | if (mode == preferred_mode) { | ||
750 | continue; | ||
751 | } | ||
752 | wlr_output_state_set_mode(state, mode); | ||
753 | if (search_adaptive_sync(ctx, output_idx)) { | ||
754 | return true; | ||
755 | } | ||
756 | } | ||
757 | |||
758 | return false; | ||
700 | } | 759 | } |
701 | 760 | ||
702 | struct output_config *find_output_config(struct sway_output *output) { | 761 | static bool search_render_format(struct search_context *ctx, size_t output_idx) { |
703 | char id[128]; | 762 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
704 | output_get_identifier(id, sizeof(id), output); | 763 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; |
705 | return get_output_config(id, output); | 764 | struct wlr_output_state *state = &backend_state->base; |
765 | struct wlr_output *wlr_output = backend_state->output; | ||
766 | |||
767 | uint32_t fmts[] = { | ||
768 | DRM_FORMAT_XRGB2101010, | ||
769 | DRM_FORMAT_XBGR2101010, | ||
770 | DRM_FORMAT_XRGB8888, | ||
771 | DRM_FORMAT_INVALID, | ||
772 | }; | ||
773 | if (render_format_is_bgr(wlr_output->render_format)) { | ||
774 | // Start with BGR in the unlikely event that we previously required it. | ||
775 | fmts[0] = DRM_FORMAT_XBGR2101010; | ||
776 | fmts[1] = DRM_FORMAT_XRGB2101010; | ||
777 | } | ||
778 | |||
779 | const struct wlr_drm_format_set *primary_formats = | ||
780 | wlr_output_get_primary_formats(wlr_output, WLR_BUFFER_CAP_DMABUF); | ||
781 | bool need_10bit = cfg->config && cfg->config->render_bit_depth == RENDER_BIT_DEPTH_10; | ||
782 | for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) { | ||
783 | if (!need_10bit && render_format_is_10bit(fmts[idx])) { | ||
784 | continue; | ||
785 | } | ||
786 | if (!wlr_drm_format_set_get(primary_formats, fmts[idx])) { | ||
787 | // This is not a supported format for this output | ||
788 | continue; | ||
789 | } | ||
790 | wlr_output_state_set_render_format(state, fmts[idx]); | ||
791 | if (search_mode(ctx, output_idx)) { | ||
792 | return true; | ||
793 | } | ||
794 | } | ||
795 | return false; | ||
706 | } | 796 | } |
707 | 797 | ||
708 | void apply_output_config_to_outputs(struct output_config *oc) { | 798 | static bool search_valid_config(struct search_context *ctx, size_t output_idx) { |
709 | // Try to find the output container and apply configuration now. If | 799 | if (output_idx >= ctx->configs_len) { |
710 | // this is during startup then there will be no container and config | 800 | // We reached the end of the search, all good! |
711 | // will be applied during normal "new output" event from wlroots. | 801 | return true; |
712 | bool wildcard = strcmp(oc->name, "*") == 0; | 802 | } |
713 | struct sway_output *sway_output, *tmp; | ||
714 | wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) { | ||
715 | if (output_match_name_or_id(sway_output, oc->name)) { | ||
716 | char id[128]; | ||
717 | output_get_identifier(id, sizeof(id), sway_output); | ||
718 | struct output_config *current = get_output_config(id, sway_output); | ||
719 | if (!current) { | ||
720 | // No stored output config matched, apply oc directly | ||
721 | sway_log(SWAY_DEBUG, "Applying oc directly"); | ||
722 | current = new_output_config(oc->name); | ||
723 | merge_output_config(current, oc); | ||
724 | } | ||
725 | apply_output_config(current, sway_output); | ||
726 | free_output_config(current); | ||
727 | 803 | ||
728 | if (!wildcard) { | 804 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
729 | // Stop looking if the output config isn't applicable to all | 805 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; |
730 | // outputs | 806 | struct wlr_output_state *state = &backend_state->base; |
731 | break; | 807 | struct wlr_output *wlr_output = backend_state->output; |
732 | } | 808 | |
809 | if (!output_config_is_disabling(cfg->config)) { | ||
810 | // Search through our possible configurations, doing a depth-first | ||
811 | // through render_format, modes, adaptive_sync and the next output's | ||
812 | // config. | ||
813 | queue_output_config(cfg->config, cfg->output, &backend_state->base); | ||
814 | if (search_render_format(ctx, output_idx)) { | ||
815 | return true; | ||
816 | } else if (!ctx->degrade_to_off) { | ||
817 | return false; | ||
818 | } | ||
819 | // We could not get anything to work, try to disable this output to see | ||
820 | // if we can at least make the outputs before us work. | ||
821 | sway_log(SWAY_DEBUG, "Unable to find valid config with output %s, disabling", | ||
822 | wlr_output->name); | ||
823 | reset_output_state(state); | ||
824 | } | ||
825 | |||
826 | wlr_output_state_set_enabled(state, false); | ||
827 | return search_finish(ctx, output_idx); | ||
828 | } | ||
829 | |||
830 | static int compare_matched_output_config_priority(const void *a, const void *b) { | ||
831 | |||
832 | const struct matched_output_config *amc = a; | ||
833 | const struct matched_output_config *bmc = b; | ||
834 | bool a_disabling = output_config_is_disabling(amc->config); | ||
835 | bool b_disabling = output_config_is_disabling(bmc->config); | ||
836 | bool a_enabled = amc->output->enabled; | ||
837 | bool b_enabled = bmc->output->enabled; | ||
838 | |||
839 | // We want to give priority to existing enabled outputs. To do so, we want | ||
840 | // the configuration order to be: | ||
841 | // 1. Existing, enabled outputs | ||
842 | // 2. Outputs that need to be enabled | ||
843 | // 3. Disabled or disabling outputs | ||
844 | if (a_enabled && !a_disabling) { | ||
845 | return -1; | ||
846 | } else if (b_enabled && !b_disabling) { | ||
847 | return 1; | ||
848 | } else if (b_disabling && !a_disabling) { | ||
849 | return -1; | ||
850 | } else if (a_disabling && !b_disabling) { | ||
851 | return 1; | ||
852 | } | ||
853 | return 0; | ||
854 | } | ||
855 | |||
856 | void sort_output_configs_by_priority(struct matched_output_config *configs, | ||
857 | size_t configs_len) { | ||
858 | qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority); | ||
859 | } | ||
860 | |||
861 | bool apply_output_configs(struct matched_output_config *configs, | ||
862 | size_t configs_len, bool test_only, bool degrade_to_off) { | ||
863 | struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); | ||
864 | if (!states) { | ||
865 | return false; | ||
866 | } | ||
867 | |||
868 | sway_log(SWAY_DEBUG, "Committing %zd outputs", configs_len); | ||
869 | for (size_t idx = 0; idx < configs_len; idx++) { | ||
870 | struct matched_output_config *cfg = &configs[idx]; | ||
871 | struct wlr_backend_output_state *backend_state = &states[idx]; | ||
872 | |||
873 | backend_state->output = cfg->output->wlr_output; | ||
874 | wlr_output_state_init(&backend_state->base); | ||
875 | |||
876 | sway_log(SWAY_DEBUG, "Preparing config for %s", | ||
877 | cfg->output->wlr_output->name); | ||
878 | queue_output_config(cfg->config, cfg->output, &backend_state->base); | ||
879 | } | ||
880 | |||
881 | struct wlr_output_swapchain_manager swapchain_mgr; | ||
882 | wlr_output_swapchain_manager_init(&swapchain_mgr, server.backend); | ||
883 | |||
884 | bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); | ||
885 | if (!ok) { | ||
886 | sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks"); | ||
887 | struct search_context ctx = { | ||
888 | .swapchain_mgr = &swapchain_mgr, | ||
889 | .states = states, | ||
890 | .configs = configs, | ||
891 | .configs_len = configs_len, | ||
892 | .degrade_to_off = degrade_to_off, | ||
893 | }; | ||
894 | if (!search_valid_config(&ctx, 0)) { | ||
895 | sway_log(SWAY_ERROR, "Search for valid config failed"); | ||
896 | goto out; | ||
733 | } | 897 | } |
734 | } | 898 | } |
735 | 899 | ||
900 | if (test_only) { | ||
901 | // The swapchain manager already did a test for us | ||
902 | goto out; | ||
903 | } | ||
904 | |||
905 | for (size_t idx = 0; idx < configs_len; idx++) { | ||
906 | struct matched_output_config *cfg = &configs[idx]; | ||
907 | struct wlr_backend_output_state *backend_state = &states[idx]; | ||
908 | |||
909 | struct wlr_scene_output_state_options opts = { | ||
910 | .swapchain = wlr_output_swapchain_manager_get_swapchain( | ||
911 | &swapchain_mgr, backend_state->output), | ||
912 | }; | ||
913 | struct wlr_scene_output *scene_output = cfg->output->scene_output; | ||
914 | struct wlr_output_state *state = &backend_state->base; | ||
915 | if (!wlr_scene_output_build_state(scene_output, state, &opts)) { | ||
916 | sway_log(SWAY_ERROR, "Building output state for '%s' failed", | ||
917 | backend_state->output->name); | ||
918 | goto out; | ||
919 | } | ||
920 | } | ||
921 | |||
922 | ok = wlr_backend_commit(server.backend, states, configs_len); | ||
923 | if (!ok) { | ||
924 | sway_log(SWAY_ERROR, "Backend commit failed"); | ||
925 | goto out; | ||
926 | } | ||
927 | |||
928 | sway_log(SWAY_DEBUG, "Commit of %zd outputs succeeded", configs_len); | ||
929 | |||
930 | wlr_output_swapchain_manager_apply(&swapchain_mgr); | ||
931 | |||
932 | for (size_t idx = 0; idx < configs_len; idx++) { | ||
933 | struct matched_output_config *cfg = &configs[idx]; | ||
934 | sway_log(SWAY_DEBUG, "Finalizing config for %s", | ||
935 | cfg->output->wlr_output->name); | ||
936 | finalize_output_config(cfg->config, cfg->output); | ||
937 | } | ||
938 | |||
939 | out: | ||
940 | wlr_output_swapchain_manager_finish(&swapchain_mgr); | ||
941 | for (size_t idx = 0; idx < configs_len; idx++) { | ||
942 | struct wlr_backend_output_state *backend_state = &states[idx]; | ||
943 | wlr_output_state_finish(&backend_state->base); | ||
944 | } | ||
945 | free(states); | ||
946 | |||
947 | // Reconfigure all devices, since input config may have been applied before | ||
948 | // this output came online, and some config items (like map_to_output) are | ||
949 | // dependent on an output being present. | ||
950 | input_manager_configure_all_input_mappings(); | ||
951 | // Reconfigure the cursor images, since the scale may have changed. | ||
952 | input_manager_configure_xcursor(); | ||
953 | |||
736 | struct sway_seat *seat; | 954 | struct sway_seat *seat; |
737 | wl_list_for_each(seat, &server.input->seats, link) { | 955 | wl_list_for_each(seat, &server.input->seats, link) { |
738 | wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); | 956 | wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); |
739 | cursor_rebase(seat->cursor); | 957 | cursor_rebase(seat->cursor); |
740 | } | 958 | } |
959 | |||
960 | return ok; | ||
741 | } | 961 | } |
742 | 962 | ||
743 | void reset_outputs(void) { | 963 | void apply_all_output_configs(void) { |
744 | struct output_config *oc = NULL; | 964 | size_t configs_len = wl_list_length(&root->all_outputs); |
745 | int i = list_seq_find(config->output_configs, output_name_cmp, "*"); | 965 | struct matched_output_config *configs = calloc(configs_len, sizeof(*configs)); |
746 | if (i >= 0) { | 966 | if (!configs) { |
747 | oc = config->output_configs->items[i]; | 967 | return; |
748 | } else { | 968 | } |
749 | oc = store_output_config(new_output_config("*")); | 969 | |
970 | int config_idx = 0; | ||
971 | struct sway_output *sway_output; | ||
972 | wl_list_for_each(sway_output, &root->all_outputs, link) { | ||
973 | if (sway_output == root->fallback_output) { | ||
974 | configs_len--; | ||
975 | continue; | ||
976 | } | ||
977 | |||
978 | struct matched_output_config *config = &configs[config_idx++]; | ||
979 | config->output = sway_output; | ||
980 | config->config = find_output_config(sway_output); | ||
981 | } | ||
982 | |||
983 | sort_output_configs_by_priority(configs, configs_len); | ||
984 | apply_output_configs(configs, configs_len, false, true); | ||
985 | for (size_t idx = 0; idx < configs_len; idx++) { | ||
986 | struct matched_output_config *cfg = &configs[idx]; | ||
987 | free_output_config(cfg->config); | ||
750 | } | 988 | } |
751 | apply_output_config_to_outputs(oc); | 989 | free(configs); |
752 | } | 990 | } |
753 | 991 | ||
754 | void free_output_config(struct output_config *oc) { | 992 | void free_output_config(struct output_config *oc) { |
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index 4b2584b6..6221b7b9 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c | |||
@@ -2,6 +2,7 @@ | |||
2 | #include <stdlib.h> | 2 | #include <stdlib.h> |
3 | #include <string.h> | 3 | #include <string.h> |
4 | #include <wayland-server-core.h> | 4 | #include <wayland-server-core.h> |
5 | #include <wlr/types/wlr_fractional_scale_v1.h> | ||
5 | #include <wlr/types/wlr_layer_shell_v1.h> | 6 | #include <wlr/types/wlr_layer_shell_v1.h> |
6 | #include <wlr/types/wlr_output.h> | 7 | #include <wlr/types/wlr_output.h> |
7 | #include <wlr/types/wlr_scene.h> | 8 | #include <wlr/types/wlr_scene.h> |
@@ -432,6 +433,12 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) { | |||
432 | 433 | ||
433 | surface->output = output; | 434 | surface->output = output; |
434 | 435 | ||
436 | // now that the surface's output is known, we can advertise its scale | ||
437 | wlr_fractional_scale_v1_notify_scale(surface->layer_surface->surface, | ||
438 | layer_surface->output->scale); | ||
439 | wlr_surface_set_preferred_buffer_scale(surface->layer_surface->surface, | ||
440 | ceil(layer_surface->output->scale)); | ||
441 | |||
435 | surface->surface_commit.notify = handle_surface_commit; | 442 | surface->surface_commit.notify = handle_surface_commit; |
436 | wl_signal_add(&layer_surface->surface->events.commit, | 443 | wl_signal_add(&layer_surface->surface->events.commit, |
437 | &surface->surface_commit); | 444 | &surface->surface_commit); |
diff --git a/sway/desktop/output.c b/sway/desktop/output.c index b8f2d32d..2722e556 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c | |||
@@ -521,9 +521,7 @@ void handle_new_output(struct wl_listener *listener, void *data) { | |||
521 | sway_session_lock_add_output(server->session_lock.lock, output); | 521 | sway_session_lock_add_output(server->session_lock.lock, output); |
522 | } | 522 | } |
523 | 523 | ||
524 | struct output_config *oc = find_output_config(output); | 524 | apply_all_output_configs(); |
525 | apply_output_config(oc, output); | ||
526 | free_output_config(oc); | ||
527 | 525 | ||
528 | transaction_commit_dirty(); | 526 | transaction_commit_dirty(); |
529 | 527 | ||
@@ -552,63 +550,89 @@ void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) { | |||
552 | wlr_output_schedule_frame(output->wlr_output); | 550 | wlr_output_schedule_frame(output->wlr_output); |
553 | } | 551 | } |
554 | 552 | ||
553 | static struct output_config *output_config_for_config_head( | ||
554 | struct wlr_output_configuration_head_v1 *config_head, | ||
555 | struct sway_output *output) { | ||
556 | struct output_config *oc = new_output_config(output->wlr_output->name); | ||
557 | oc->enabled = config_head->state.enabled; | ||
558 | if (!oc->enabled) { | ||
559 | return oc; | ||
560 | } | ||
561 | |||
562 | if (config_head->state.mode != NULL) { | ||
563 | struct wlr_output_mode *mode = config_head->state.mode; | ||
564 | oc->width = mode->width; | ||
565 | oc->height = mode->height; | ||
566 | oc->refresh_rate = mode->refresh / 1000.f; | ||
567 | } else { | ||
568 | oc->width = config_head->state.custom_mode.width; | ||
569 | oc->height = config_head->state.custom_mode.height; | ||
570 | oc->refresh_rate = | ||
571 | config_head->state.custom_mode.refresh / 1000.f; | ||
572 | } | ||
573 | oc->x = config_head->state.x; | ||
574 | oc->y = config_head->state.y; | ||
575 | oc->transform = config_head->state.transform; | ||
576 | oc->scale = config_head->state.scale; | ||
577 | oc->adaptive_sync = config_head->state.adaptive_sync_enabled; | ||
578 | return oc; | ||
579 | } | ||
580 | |||
555 | static void output_manager_apply(struct sway_server *server, | 581 | static void output_manager_apply(struct sway_server *server, |
556 | struct wlr_output_configuration_v1 *config, bool test_only) { | 582 | struct wlr_output_configuration_v1 *config, bool test_only) { |
557 | // TODO: perform atomic tests on the whole backend atomically | 583 | size_t configs_len = wl_list_length(&root->all_outputs); |
558 | 584 | struct matched_output_config *configs = calloc(configs_len, sizeof(*configs)); | |
559 | struct wlr_output_configuration_head_v1 *config_head; | 585 | if (!configs) { |
560 | // First disable outputs we need to disable | 586 | return; |
561 | bool ok = true; | 587 | } |
562 | wl_list_for_each(config_head, &config->heads, link) { | 588 | |
563 | struct wlr_output *wlr_output = config_head->state.output; | 589 | int config_idx = 0; |
564 | struct sway_output *output = wlr_output->data; | 590 | struct sway_output *sway_output; |
565 | if (!output->enabled || config_head->state.enabled) { | 591 | wl_list_for_each(sway_output, &root->all_outputs, link) { |
592 | if (sway_output == root->fallback_output) { | ||
593 | configs_len--; | ||
566 | continue; | 594 | continue; |
567 | } | 595 | } |
568 | struct output_config *oc = new_output_config(output->wlr_output->name); | ||
569 | oc->enabled = false; | ||
570 | 596 | ||
571 | if (test_only) { | 597 | struct matched_output_config *cfg = &configs[config_idx++]; |
572 | ok &= test_output_config(oc, output); | 598 | cfg->output = sway_output; |
573 | } else { | 599 | |
574 | oc = store_output_config(oc); | 600 | struct wlr_output_configuration_head_v1 *config_head; |
575 | ok &= apply_output_config(oc, output); | 601 | wl_list_for_each(config_head, &config->heads, link) { |
602 | if (config_head->state.output == sway_output->wlr_output) { | ||
603 | cfg->config = output_config_for_config_head(config_head, sway_output); | ||
604 | break; | ||
605 | } | ||
606 | } | ||
607 | if (!cfg->config) { | ||
608 | cfg->config = find_output_config(sway_output); | ||
576 | } | 609 | } |
577 | } | 610 | } |
578 | 611 | ||
579 | // Then enable outputs that need to | 612 | sort_output_configs_by_priority(configs, configs_len); |
580 | wl_list_for_each(config_head, &config->heads, link) { | 613 | bool ok = apply_output_configs(configs, configs_len, test_only, false); |
581 | struct wlr_output *wlr_output = config_head->state.output; | 614 | for (size_t idx = 0; idx < configs_len; idx++) { |
582 | struct sway_output *output = wlr_output->data; | 615 | struct matched_output_config *cfg = &configs[idx]; |
583 | if (!config_head->state.enabled) { | 616 | |
584 | continue; | 617 | // Only store new configs for successful non-test commits. Old configs, |
585 | } | 618 | // test-only and failed commits just get freed. |
586 | struct output_config *oc = new_output_config(output->wlr_output->name); | 619 | bool store_config = false; |
587 | oc->enabled = true; | 620 | if (!test_only && ok) { |
588 | if (config_head->state.mode != NULL) { | 621 | struct wlr_output_configuration_head_v1 *config_head; |
589 | struct wlr_output_mode *mode = config_head->state.mode; | 622 | wl_list_for_each(config_head, &config->heads, link) { |
590 | oc->width = mode->width; | 623 | if (config_head->state.output == cfg->output->wlr_output) { |
591 | oc->height = mode->height; | 624 | store_config = true; |
592 | oc->refresh_rate = mode->refresh / 1000.f; | 625 | break; |
593 | } else { | 626 | } |
594 | oc->width = config_head->state.custom_mode.width; | 627 | } |
595 | oc->height = config_head->state.custom_mode.height; | ||
596 | oc->refresh_rate = | ||
597 | config_head->state.custom_mode.refresh / 1000.f; | ||
598 | } | 628 | } |
599 | oc->x = config_head->state.x; | 629 | if (store_config) { |
600 | oc->y = config_head->state.y; | 630 | store_output_config(cfg->config); |
601 | oc->transform = config_head->state.transform; | ||
602 | oc->scale = config_head->state.scale; | ||
603 | oc->adaptive_sync = config_head->state.adaptive_sync_enabled; | ||
604 | |||
605 | if (test_only) { | ||
606 | ok &= test_output_config(oc, output); | ||
607 | } else { | 631 | } else { |
608 | oc = store_output_config(oc); | 632 | free_output_config(cfg->config); |
609 | ok &= apply_output_config(oc, output); | ||
610 | } | 633 | } |
611 | } | 634 | } |
635 | free(configs); | ||
612 | 636 | ||
613 | if (ok) { | 637 | if (ok) { |
614 | wlr_output_configuration_v1_send_succeeded(config); | 638 | wlr_output_configuration_v1_send_succeeded(config); |
@@ -652,6 +676,6 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, | |||
652 | oc->power = 1; | 676 | oc->power = 1; |
653 | break; | 677 | break; |
654 | } | 678 | } |
655 | oc = store_output_config(oc); | 679 | store_output_config(oc); |
656 | apply_output_config(oc, output); | 680 | apply_all_output_configs(); |
657 | } | 681 | } |
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index b97f0152..f74d0658 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c | |||
@@ -32,6 +32,7 @@ static struct modifier_key { | |||
32 | { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, | 32 | { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, |
33 | { "Mod3", WLR_MODIFIER_MOD3 }, | 33 | { "Mod3", WLR_MODIFIER_MOD3 }, |
34 | { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, | 34 | { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, |
35 | { "Super", WLR_MODIFIER_LOGO }, | ||
35 | { "Mod5", WLR_MODIFIER_MOD5 }, | 36 | { "Mod5", WLR_MODIFIER_MOD5 }, |
36 | }; | 37 | }; |
37 | 38 | ||
diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c index 35fd3bcb..340e334b 100644 --- a/sway/input/seatop_down.c +++ b/sway/input/seatop_down.c | |||
@@ -117,7 +117,11 @@ static void handle_touch_cancel(struct sway_seat *seat, | |||
117 | } | 117 | } |
118 | 118 | ||
119 | if (e->surface) { | 119 | if (e->surface) { |
120 | wlr_seat_touch_notify_cancel(seat->wlr_seat, e->surface); | 120 | struct wl_client *client = wl_resource_get_client(e->surface->resource); |
121 | struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat->wlr_seat, client); | ||
122 | if (seat_client != NULL) { | ||
123 | wlr_seat_touch_notify_cancel(seat->wlr_seat, seat_client); | ||
124 | } | ||
121 | } | 125 | } |
122 | 126 | ||
123 | if (wl_list_empty(&e->point_events)) { | 127 | if (wl_list_empty(&e->point_events)) { |
diff --git a/sway/server.c b/sway/server.c index d159dc9b..180d3a6b 100644 --- a/sway/server.c +++ b/sway/server.c | |||
@@ -240,13 +240,12 @@ bool server_init(struct sway_server *server) { | |||
240 | 240 | ||
241 | wlr_renderer_init_wl_shm(server->renderer, server->wl_display); | 241 | wlr_renderer_init_wl_shm(server->renderer, server->wl_display); |
242 | 242 | ||
243 | if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL) { | 243 | if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) { |
244 | server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( | 244 | server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( |
245 | server->wl_display, 4, server->renderer); | 245 | server->wl_display, 4, server->renderer); |
246 | } | 246 | if (debug.legacy_wl_drm) { |
247 | if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL && | 247 | wlr_drm_create(server->wl_display, server->renderer); |
248 | debug.legacy_wl_drm) { | 248 | } |
249 | wlr_drm_create(server->wl_display, server->renderer); | ||
250 | } | 249 | } |
251 | 250 | ||
252 | server->allocator = wlr_allocator_autocreate(server->backend, | 251 | server->allocator = wlr_allocator_autocreate(server->backend, |
diff --git a/sway/sway-ipc.7.scd b/sway/sway-ipc.7.scd index c9895e52..2f697248 100644 --- a/sway/sway-ipc.7.scd +++ b/sway/sway-ipc.7.scd | |||
@@ -1046,7 +1046,7 @@ An object with a single string property containing the contents of the config | |||
1046 | *Example Reply:* | 1046 | *Example Reply:* |
1047 | ``` | 1047 | ``` |
1048 | { | 1048 | { |
1049 | "config": "set $mod Mod4\nbindsym $mod+q exit\n" | 1049 | "config": "set $mod Mod4\\nbindsym $mod+q exit\\n" |
1050 | } | 1050 | } |
1051 | ``` | 1051 | ``` |
1052 | 1052 | ||
diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 7e58b528..9f823947 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd | |||
@@ -400,6 +400,12 @@ runtime. | |||
400 | only be available for that group. By default, if you overwrite a binding, | 400 | only be available for that group. By default, if you overwrite a binding, |
401 | swaynag will give you a warning. To silence this, use the _--no-warn_ flag. | 401 | swaynag will give you a warning. To silence this, use the _--no-warn_ flag. |
402 | 402 | ||
403 | For specifying modifier keys, you can use the XKB modifier names _Shift_, | ||
404 | _Lock_ (for Caps Lock), _Control_, _Mod1_ (for Alt), _Mod2_ (for Num Lock), | ||
405 | _Mod3_ (for XKB modifier Mod3), _Mod4_ (for the Logo key), and _Mod5_ (for | ||
406 | AltGr). In addition, you can use the aliases _Ctrl_ (for Control), _Alt_ | ||
407 | (for Alt), and _Super_ (for the Logo key). | ||
408 | |||
403 | Unless the flag _--locked_ is set, the command will not be run when a | 409 | Unless the flag _--locked_ is set, the command will not be run when a |
404 | screen locking program is active. If there is a matching binding with | 410 | screen locking program is active. If there is a matching binding with |
405 | and without _--locked_, the one with will be preferred when locked and the | 411 | and without _--locked_, the one with will be preferred when locked and the |
diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c index 5eba53ba..4b7ee999 100644 --- a/sway/sway_text_node.c +++ b/sway/sway_text_node.c | |||
@@ -58,7 +58,7 @@ struct text_buffer { | |||
58 | 58 | ||
59 | static int get_text_width(struct sway_text_node *props) { | 59 | static int get_text_width(struct sway_text_node *props) { |
60 | int width = props->width; | 60 | int width = props->width; |
61 | if (props->max_width) { | 61 | if (props->max_width >= 0) { |
62 | width = MIN(width, props->max_width); | 62 | width = MIN(width, props->max_width); |
63 | } | 63 | } |
64 | return MAX(width, 0); | 64 | return MAX(width, 0); |
@@ -81,6 +81,11 @@ static void render_backing_buffer(struct text_buffer *buffer) { | |||
81 | return; | 81 | return; |
82 | } | 82 | } |
83 | 83 | ||
84 | if (buffer->props.max_width == 0) { | ||
85 | wlr_scene_buffer_set_buffer(buffer->buffer_node, NULL); | ||
86 | return; | ||
87 | } | ||
88 | |||
84 | float scale = buffer->scale; | 89 | float scale = buffer->scale; |
85 | int width = ceil(buffer->props.width * scale); | 90 | int width = ceil(buffer->props.width * scale); |
86 | int height = ceil(buffer->props.height * scale); | 91 | int height = ceil(buffer->props.height * scale); |
@@ -236,6 +241,7 @@ struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, | |||
236 | 241 | ||
237 | buffer->buffer_node = node; | 242 | buffer->buffer_node = node; |
238 | buffer->props.node = &node->node; | 243 | buffer->props.node = &node->node; |
244 | buffer->props.max_width = -1; | ||
239 | buffer->text = strdup(text); | 245 | buffer->text = strdup(text); |
240 | if (!buffer->text) { | 246 | if (!buffer->text) { |
241 | free(buffer); | 247 | free(buffer); |
diff --git a/sway/tree/container.c b/sway/tree/container.c index 9224b4fb..80ef34fe 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c | |||
@@ -352,6 +352,8 @@ void container_arrange_title_bar(struct sway_container *con) { | |||
352 | 352 | ||
353 | int alloc_width = MIN((int)node->width, | 353 | int alloc_width = MIN((int)node->width, |
354 | width - h_padding - config->titlebar_h_padding); | 354 | width - h_padding - config->titlebar_h_padding); |
355 | alloc_width = MAX(alloc_width, 0); | ||
356 | |||
355 | sway_text_node_set_max_width(node, alloc_width); | 357 | sway_text_node_set_max_width(node, alloc_width); |
356 | wlr_scene_node_set_position(node->node, | 358 | wlr_scene_node_set_position(node->node, |
357 | h_padding, (height - node->height) >> 1); | 359 | h_padding, (height - node->height) >> 1); |
@@ -376,6 +378,8 @@ void container_arrange_title_bar(struct sway_container *con) { | |||
376 | 378 | ||
377 | int alloc_width = MIN((int) node->width, | 379 | int alloc_width = MIN((int) node->width, |
378 | width - h_padding - config->titlebar_h_padding); | 380 | width - h_padding - config->titlebar_h_padding); |
381 | alloc_width = MAX(alloc_width, 0); | ||
382 | |||
379 | sway_text_node_set_max_width(node, alloc_width); | 383 | sway_text_node_set_max_width(node, alloc_width); |
380 | wlr_scene_node_set_position(node->node, | 384 | wlr_scene_node_set_position(node->node, |
381 | h_padding, (height - node->height) >> 1); | 385 | h_padding, (height - node->height) >> 1); |