diff options
Diffstat (limited to 'sway/config/output.c')
-rw-r--r-- | sway/config/output.c | 935 |
1 files changed, 637 insertions, 298 deletions
diff --git a/sway/config/output.c b/sway/config/output.c index c9ec6745..fb1956df 100644 --- a/sway/config/output.c +++ b/sway/config/output.c | |||
@@ -1,13 +1,15 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <assert.h> | 1 | #include <assert.h> |
2 | #include <drm_fourcc.h> | ||
3 | #include <stdbool.h> | 3 | #include <stdbool.h> |
4 | #include <string.h> | 4 | #include <string.h> |
5 | #include <sys/socket.h> | 5 | #include <sys/socket.h> |
6 | #include <sys/wait.h> | 6 | #include <sys/wait.h> |
7 | #include <unistd.h> | 7 | #include <unistd.h> |
8 | #include <wlr/config.h> | ||
8 | #include <wlr/types/wlr_cursor.h> | 9 | #include <wlr/types/wlr_cursor.h> |
9 | #include <wlr/types/wlr_output_layout.h> | 10 | #include <wlr/types/wlr_output_layout.h> |
10 | #include <wlr/types/wlr_output.h> | 11 | #include <wlr/types/wlr_output.h> |
12 | #include <wlr/types/wlr_output_swapchain_manager.h> | ||
11 | #include "sway/config.h" | 13 | #include "sway/config.h" |
12 | #include "sway/input/cursor.h" | 14 | #include "sway/input/cursor.h" |
13 | #include "sway/output.h" | 15 | #include "sway/output.h" |
@@ -15,6 +17,10 @@ | |||
15 | #include "log.h" | 17 | #include "log.h" |
16 | #include "util.h" | 18 | #include "util.h" |
17 | 19 | ||
20 | #if WLR_HAS_DRM_BACKEND | ||
21 | #include <wlr/backend/drm.h> | ||
22 | #endif | ||
23 | |||
18 | int output_name_cmp(const void *item, const void *data) { | 24 | int output_name_cmp(const void *item, const void *data) { |
19 | const struct output_config *output = item; | 25 | const struct output_config *output = item; |
20 | const char *name = data; | 26 | const char *name = data; |
@@ -25,8 +31,10 @@ int output_name_cmp(const void *item, const void *data) { | |||
25 | void output_get_identifier(char *identifier, size_t len, | 31 | void output_get_identifier(char *identifier, size_t len, |
26 | struct sway_output *output) { | 32 | struct sway_output *output) { |
27 | struct wlr_output *wlr_output = output->wlr_output; | 33 | struct wlr_output *wlr_output = output->wlr_output; |
28 | snprintf(identifier, len, "%s %s %s", wlr_output->make, wlr_output->model, | 34 | snprintf(identifier, len, "%s %s %s", |
29 | wlr_output->serial); | 35 | wlr_output->make ? wlr_output->make : "Unknown", |
36 | wlr_output->model ? wlr_output->model : "Unknown", | ||
37 | wlr_output->serial ? wlr_output->serial : "Unknown"); | ||
30 | } | 38 | } |
31 | 39 | ||
32 | const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) { | 40 | const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) { |
@@ -58,6 +66,7 @@ struct output_config *new_output_config(const char *name) { | |||
58 | oc->width = oc->height = -1; | 66 | oc->width = oc->height = -1; |
59 | oc->refresh_rate = -1; | 67 | oc->refresh_rate = -1; |
60 | oc->custom_mode = -1; | 68 | oc->custom_mode = -1; |
69 | oc->drm_mode.type = -1; | ||
61 | oc->x = oc->y = -1; | 70 | oc->x = oc->y = -1; |
62 | oc->scale = -1; | 71 | oc->scale = -1; |
63 | oc->scale_filter = SCALE_FILTER_DEFAULT; | 72 | oc->scale_filter = SCALE_FILTER_DEFAULT; |
@@ -65,10 +74,77 @@ struct output_config *new_output_config(const char *name) { | |||
65 | oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; | 74 | oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; |
66 | oc->max_render_time = -1; | 75 | oc->max_render_time = -1; |
67 | oc->adaptive_sync = -1; | 76 | oc->adaptive_sync = -1; |
77 | oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; | ||
78 | oc->power = -1; | ||
68 | return oc; | 79 | return oc; |
69 | } | 80 | } |
70 | 81 | ||
71 | 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) { | ||
72 | if (src->enabled != -1) { | 148 | if (src->enabled != -1) { |
73 | dst->enabled = src->enabled; | 149 | dst->enabled = src->enabled; |
74 | } | 150 | } |
@@ -99,6 +175,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { | |||
99 | if (src->custom_mode != -1) { | 175 | if (src->custom_mode != -1) { |
100 | dst->custom_mode = src->custom_mode; | 176 | dst->custom_mode = src->custom_mode; |
101 | } | 177 | } |
178 | if (src->drm_mode.type != (uint32_t) -1) { | ||
179 | memcpy(&dst->drm_mode, &src->drm_mode, sizeof(src->drm_mode)); | ||
180 | } | ||
102 | if (src->transform != -1) { | 181 | if (src->transform != -1) { |
103 | dst->transform = src->transform; | 182 | dst->transform = src->transform; |
104 | } | 183 | } |
@@ -108,6 +187,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { | |||
108 | if (src->adaptive_sync != -1) { | 187 | if (src->adaptive_sync != -1) { |
109 | dst->adaptive_sync = src->adaptive_sync; | 188 | dst->adaptive_sync = src->adaptive_sync; |
110 | } | 189 | } |
190 | if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { | ||
191 | dst->render_bit_depth = src->render_bit_depth; | ||
192 | } | ||
111 | if (src->background) { | 193 | if (src->background) { |
112 | free(dst->background); | 194 | free(dst->background); |
113 | dst->background = strdup(src->background); | 195 | dst->background = strdup(src->background); |
@@ -120,155 +202,124 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { | |||
120 | free(dst->background_fallback); | 202 | free(dst->background_fallback); |
121 | dst->background_fallback = strdup(src->background_fallback); | 203 | dst->background_fallback = strdup(src->background_fallback); |
122 | } | 204 | } |
123 | if (src->dpms_state != 0) { | 205 | if (src->power != -1) { |
124 | dst->dpms_state = src->dpms_state; | 206 | dst->power = src->power; |
125 | } | 207 | } |
126 | } | 208 | } |
127 | 209 | ||
128 | static void merge_wildcard_on_all(struct output_config *wildcard) { | 210 | void store_output_config(struct output_config *oc) { |
129 | for (int i = 0; i < config->output_configs->length; i++) { | 211 | bool merged = false; |
130 | struct output_config *oc = config->output_configs->items[i]; | 212 | bool wildcard = strcmp(oc->name, "*") == 0; |
131 | if (strcmp(wildcard->name, oc->name) != 0) { | 213 | struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name); |
132 | sway_log(SWAY_DEBUG, "Merging output * config on %s", oc->name); | ||
133 | merge_output_config(oc, wildcard); | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | 214 | ||
138 | static void merge_id_on_name(struct output_config *oc) { | ||
139 | char *id_on_name = NULL; | ||
140 | char id[128]; | 215 | char id[128]; |
141 | char *name = NULL; | 216 | if (output) { |
142 | struct sway_output *output; | ||
143 | wl_list_for_each(output, &root->all_outputs, link) { | ||
144 | name = output->wlr_output->name; | ||
145 | output_get_identifier(id, sizeof(id), output); | 217 | output_get_identifier(id, sizeof(id), output); |
146 | if (strcmp(name, oc->name) == 0 || strcmp(id, oc->name) == 0) { | ||
147 | size_t length = snprintf(NULL, 0, "%s on %s", id, name) + 1; | ||
148 | id_on_name = malloc(length); | ||
149 | if (!id_on_name) { | ||
150 | sway_log(SWAY_ERROR, "Failed to allocate id on name string"); | ||
151 | return; | ||
152 | } | ||
153 | snprintf(id_on_name, length, "%s on %s", id, name); | ||
154 | break; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | if (!id_on_name) { | ||
159 | return; | ||
160 | } | 218 | } |
161 | 219 | ||
162 | 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++) { |
163 | if (i >= 0) { | 221 | struct output_config *old = config->output_configs->items[i]; |
164 | sway_log(SWAY_DEBUG, "Merging on top of existing id on name config"); | 222 | |
165 | merge_output_config(config->output_configs->items[i], oc); | 223 | // If the old config matches the new config's name, regardless of |
166 | } else { | 224 | // whether it was name or identifier, merge on top of the existing |
167 | // 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 |
168 | int ni = list_seq_find(config->output_configs, output_name_cmp, name); | 226 | // old wildcard configs. |
169 | int ii = list_seq_find(config->output_configs, output_name_cmp, id); | 227 | if (strcmp(old->name, oc->name) == 0) { |
170 | if ((ni >= 0 && ii >= 0) || (ni >= 0 && strcmp(oc->name, id) == 0) | 228 | merge_output_config(old, oc); |
171 | || (ii >= 0 && strcmp(oc->name, name) == 0)) { | 229 | merged = true; |
172 | struct output_config *ion_oc = new_output_config(id_on_name); | 230 | continue; |
173 | if (ni >= 0) { | ||
174 | merge_output_config(ion_oc, config->output_configs->items[ni]); | ||
175 | } | ||
176 | if (ii >= 0) { | ||
177 | merge_output_config(ion_oc, config->output_configs->items[ii]); | ||
178 | } | ||
179 | merge_output_config(ion_oc, oc); | ||
180 | list_add(config->output_configs, ion_oc); | ||
181 | sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" | ||
182 | " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " | ||
183 | "transform %d) (bg %s %s) (dpms %d) (max render time: %d)", | ||
184 | ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height, | ||
185 | ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, | ||
186 | ion_oc->transform, ion_oc->background, | ||
187 | ion_oc->background_option, ion_oc->dpms_state, | ||
188 | ion_oc->max_render_time); | ||
189 | } | 231 | } |
190 | } | ||
191 | free(id_on_name); | ||
192 | } | ||
193 | 232 | ||
194 | struct output_config *store_output_config(struct output_config *oc) { | 233 | // If the new config is a wildcard config we supersede all non-wildcard |
195 | bool wildcard = strcmp(oc->name, "*") == 0; | 234 | // configs. Old wildcard configs have already been handled above. |
196 | if (wildcard) { | 235 | if (wildcard) { |
197 | merge_wildcard_on_all(oc); | 236 | supersede_output_config(old, oc); |
198 | } else { | 237 | continue; |
199 | merge_id_on_name(oc); | 238 | } |
200 | } | ||
201 | 239 | ||
202 | 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 |
203 | if (i >= 0) { | 241 | // matches on that output's identifier, supersede it. |
204 | sway_log(SWAY_DEBUG, "Merging on top of existing output config"); | 242 | if (output && strcmp(old->name, id) == 0 && |
205 | struct output_config *current = config->output_configs->items[i]; | 243 | strcmp(oc->name, output->wlr_output->name) == 0) { |
206 | merge_output_config(current, oc); | 244 | supersede_output_config(old, oc); |
207 | free_output_config(oc); | ||
208 | oc = current; | ||
209 | } else if (!wildcard) { | ||
210 | sway_log(SWAY_DEBUG, "Adding non-wildcard output config"); | ||
211 | i = list_seq_find(config->output_configs, output_name_cmp, "*"); | ||
212 | if (i >= 0) { | ||
213 | sway_log(SWAY_DEBUG, "Merging on top of output * config"); | ||
214 | struct output_config *current = new_output_config(oc->name); | ||
215 | merge_output_config(current, config->output_configs->items[i]); | ||
216 | merge_output_config(current, oc); | ||
217 | free_output_config(oc); | ||
218 | oc = current; | ||
219 | } | 245 | } |
220 | list_add(config->output_configs, oc); | ||
221 | } else { | ||
222 | // New wildcard config. Just add it | ||
223 | sway_log(SWAY_DEBUG, "Adding output * config"); | ||
224 | list_add(config->output_configs, oc); | ||
225 | } | 246 | } |
226 | 247 | ||
227 | 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 " |
228 | "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d) " | 249 | "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) " |
229 | "(max render time: %d)", | 250 | "(max render time: %d)", |
230 | oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, | 251 | oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, |
231 | oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), | 252 | oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), |
232 | oc->transform, oc->background, oc->background_option, oc->dpms_state, | 253 | oc->transform, oc->background, oc->background_option, oc->power, |
233 | oc->max_render_time); | 254 | oc->max_render_time); |
234 | 255 | ||
235 | 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 | } | ||
236 | } | 263 | } |
237 | 264 | ||
238 | static void set_mode(struct wlr_output *output, int width, int height, | 265 | static void set_mode(struct wlr_output *output, struct wlr_output_state *pending, |
239 | float refresh_rate, bool custom) { | 266 | int width, int height, float refresh_rate, bool custom) { |
240 | // Not all floating point integers can be represented exactly | 267 | // Not all floating point integers can be represented exactly |
241 | // as (int)(1000 * mHz / 1000.f) | 268 | // as (int)(1000 * mHz / 1000.f) |
242 | // round() the result to avoid any error | 269 | // round() the result to avoid any error |
243 | int mhz = (int)round(refresh_rate * 1000); | 270 | int mhz = (int)roundf(refresh_rate * 1000); |
271 | // If no target refresh rate is given, match highest available | ||
272 | mhz = mhz <= 0 ? INT_MAX : mhz; | ||
244 | 273 | ||
245 | if (wl_list_empty(&output->modes) || custom) { | 274 | if (wl_list_empty(&output->modes) || custom) { |
246 | sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name); | 275 | sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name); |
247 | wlr_output_set_custom_mode(output, width, height, | 276 | wlr_output_state_set_custom_mode(pending, width, height, |
248 | refresh_rate > 0 ? mhz : 0); | 277 | refresh_rate > 0 ? mhz : 0); |
249 | return; | 278 | return; |
250 | } | 279 | } |
251 | 280 | ||
252 | struct wlr_output_mode *mode, *best = NULL; | 281 | struct wlr_output_mode *mode, *best = NULL; |
282 | int best_diff_mhz = INT_MAX; | ||
253 | wl_list_for_each(mode, &output->modes, link) { | 283 | wl_list_for_each(mode, &output->modes, link) { |
254 | if (mode->width == width && mode->height == height) { | 284 | if (mode->width == width && mode->height == height) { |
255 | if (mode->refresh == mhz) { | 285 | int diff_mhz = abs(mode->refresh - mhz); |
256 | best = mode; | 286 | if (diff_mhz < best_diff_mhz) { |
257 | break; | 287 | best_diff_mhz = diff_mhz; |
258 | } | ||
259 | if (best == NULL || mode->refresh > best->refresh) { | ||
260 | best = mode; | 288 | best = mode; |
289 | if (best_diff_mhz == 0) { | ||
290 | break; | ||
291 | } | ||
261 | } | 292 | } |
262 | } | 293 | } |
263 | } | 294 | } |
264 | if (!best) { | 295 | if (best) { |
265 | sway_log(SWAY_ERROR, "Configured mode for %s not available", output->name); | 296 | sway_log(SWAY_INFO, "Assigning configured mode (%dx%d@%.3fHz) to %s", |
266 | sway_log(SWAY_INFO, "Picking preferred mode instead"); | 297 | best->width, best->height, best->refresh / 1000.f, output->name); |
267 | best = wlr_output_preferred_mode(output); | ||
268 | } else { | 298 | } else { |
269 | sway_log(SWAY_DEBUG, "Assigning configured mode to %s", output->name); | 299 | best = wlr_output_preferred_mode(output); |
300 | sway_log(SWAY_INFO, "Configured mode (%dx%d@%.3fHz) not available, " | ||
301 | "applying preferred mode (%dx%d@%.3fHz)", | ||
302 | width, height, refresh_rate, | ||
303 | best->width, best->height, best->refresh / 1000.f); | ||
304 | } | ||
305 | wlr_output_state_set_mode(pending, best); | ||
306 | } | ||
307 | |||
308 | static void set_modeline(struct wlr_output *output, | ||
309 | struct wlr_output_state *pending, drmModeModeInfo *drm_mode) { | ||
310 | #if WLR_HAS_DRM_BACKEND | ||
311 | if (!wlr_output_is_drm(output)) { | ||
312 | sway_log(SWAY_ERROR, "Modeline can only be set to DRM output"); | ||
313 | return; | ||
314 | } | ||
315 | sway_log(SWAY_DEBUG, "Assigning custom modeline to %s", output->name); | ||
316 | struct wlr_output_mode *mode = wlr_drm_connector_add_mode(output, drm_mode); | ||
317 | if (mode) { | ||
318 | wlr_output_state_set_mode(pending, mode); | ||
270 | } | 319 | } |
271 | wlr_output_set_mode(output, best); | 320 | #else |
321 | sway_log(SWAY_ERROR, "Modeline can only be set to DRM output"); | ||
322 | #endif | ||
272 | } | 323 | } |
273 | 324 | ||
274 | /* Some manufacturers hardcode the aspect-ratio of the output in the physical | 325 | /* Some manufacturers hardcode the aspect-ratio of the output in the physical |
@@ -289,23 +340,24 @@ static bool phys_size_is_aspect_ratio(struct wlr_output *output) { | |||
289 | // 1 inch = 25.4 mm | 340 | // 1 inch = 25.4 mm |
290 | #define MM_PER_INCH 25.4 | 341 | #define MM_PER_INCH 25.4 |
291 | 342 | ||
292 | static int compute_default_scale(struct wlr_output *output) { | 343 | static int compute_default_scale(struct wlr_output *output, |
344 | struct wlr_output_state *pending) { | ||
293 | struct wlr_box box = { .width = output->width, .height = output->height }; | 345 | struct wlr_box box = { .width = output->width, .height = output->height }; |
294 | if (output->pending.committed & WLR_OUTPUT_STATE_MODE) { | 346 | if (pending->committed & WLR_OUTPUT_STATE_MODE) { |
295 | switch (output->pending.mode_type) { | 347 | switch (pending->mode_type) { |
296 | case WLR_OUTPUT_STATE_MODE_FIXED: | 348 | case WLR_OUTPUT_STATE_MODE_FIXED: |
297 | box.width = output->pending.mode->width; | 349 | box.width = pending->mode->width; |
298 | box.height = output->pending.mode->height; | 350 | box.height = pending->mode->height; |
299 | break; | 351 | break; |
300 | case WLR_OUTPUT_STATE_MODE_CUSTOM: | 352 | case WLR_OUTPUT_STATE_MODE_CUSTOM: |
301 | box.width = output->pending.custom_mode.width; | 353 | box.width = pending->custom_mode.width; |
302 | box.height = output->pending.custom_mode.height; | 354 | box.height = pending->custom_mode.height; |
303 | break; | 355 | break; |
304 | } | 356 | } |
305 | } | 357 | } |
306 | enum wl_output_transform transform = output->transform; | 358 | enum wl_output_transform transform = output->transform; |
307 | if (output->pending.committed & WLR_OUTPUT_STATE_TRANSFORM) { | 359 | if (pending->committed & WLR_OUTPUT_STATE_TRANSFORM) { |
308 | transform = output->pending.transform; | 360 | transform = pending->transform; |
309 | } | 361 | } |
310 | wlr_box_transform(&box, &box, transform, box.width, box.height); | 362 | wlr_box_transform(&box, &box, transform, box.width, box.height); |
311 | 363 | ||
@@ -334,42 +386,70 @@ static int compute_default_scale(struct wlr_output *output) { | |||
334 | return 2; | 386 | return 2; |
335 | } | 387 | } |
336 | 388 | ||
389 | static bool render_format_is_10bit(uint32_t render_format) { | ||
390 | return render_format == DRM_FORMAT_XRGB2101010 || | ||
391 | render_format == DRM_FORMAT_XBGR2101010; | ||
392 | } | ||
393 | |||
394 | static bool render_format_is_bgr(uint32_t fmt) { | ||
395 | return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888; | ||
396 | } | ||
397 | |||
398 | static bool output_config_is_disabling(struct output_config *oc) { | ||
399 | return oc && (!oc->enabled || oc->power == 0); | ||
400 | } | ||
401 | |||
337 | static void queue_output_config(struct output_config *oc, | 402 | static void queue_output_config(struct output_config *oc, |
338 | struct sway_output *output) { | 403 | struct sway_output *output, struct wlr_output_state *pending) { |
339 | if (output == root->noop_output) { | 404 | if (output == root->fallback_output) { |
340 | return; | 405 | return; |
341 | } | 406 | } |
342 | 407 | ||
343 | struct wlr_output *wlr_output = output->wlr_output; | 408 | struct wlr_output *wlr_output = output->wlr_output; |
344 | 409 | ||
345 | if (oc && (!oc->enabled || oc->dpms_state == DPMS_OFF)) { | 410 | if (output_config_is_disabling(oc)) { |
346 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); | 411 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); |
347 | wlr_output_enable(wlr_output, false); | 412 | wlr_output_state_set_enabled(pending, false); |
348 | return; | 413 | return; |
349 | } | 414 | } |
350 | 415 | ||
351 | sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name); | 416 | sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name); |
352 | wlr_output_enable(wlr_output, true); | 417 | wlr_output_state_set_enabled(pending, true); |
353 | 418 | ||
354 | if (oc && oc->width > 0 && oc->height > 0) { | 419 | if (oc && oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t) -1) { |
420 | sway_log(SWAY_DEBUG, "Set %s modeline", | ||
421 | wlr_output->name); | ||
422 | set_modeline(wlr_output, pending, &oc->drm_mode); | ||
423 | } else if (oc && oc->width > 0 && oc->height > 0) { | ||
355 | sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)", | 424 | sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)", |
356 | wlr_output->name, oc->width, oc->height, oc->refresh_rate); | 425 | wlr_output->name, oc->width, oc->height, oc->refresh_rate); |
357 | set_mode(wlr_output, oc->width, oc->height, | 426 | set_mode(wlr_output, pending, oc->width, oc->height, |
358 | oc->refresh_rate, oc->custom_mode == 1); | 427 | oc->refresh_rate, oc->custom_mode == 1); |
359 | } else if (!wl_list_empty(&wlr_output->modes)) { | 428 | } else if (!wl_list_empty(&wlr_output->modes)) { |
360 | struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); | 429 | sway_log(SWAY_DEBUG, "Set preferred mode"); |
361 | wlr_output_set_mode(wlr_output, mode); | 430 | struct wlr_output_mode *preferred_mode = |
431 | wlr_output_preferred_mode(wlr_output); | ||
432 | wlr_output_state_set_mode(pending, preferred_mode); | ||
362 | } | 433 | } |
363 | 434 | ||
364 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { | 435 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { |
365 | sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name, | 436 | sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name, |
366 | sway_wl_output_subpixel_to_string(oc->subpixel)); | 437 | sway_wl_output_subpixel_to_string(oc->subpixel)); |
367 | wlr_output_set_subpixel(wlr_output, oc->subpixel); | 438 | wlr_output_state_set_subpixel(pending, oc->subpixel); |
368 | } | 439 | } |
369 | 440 | ||
441 | enum wl_output_transform tr = WL_OUTPUT_TRANSFORM_NORMAL; | ||
370 | if (oc && oc->transform >= 0) { | 442 | if (oc && oc->transform >= 0) { |
371 | sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, oc->transform); | 443 | tr = oc->transform; |
372 | wlr_output_set_transform(wlr_output, oc->transform); | 444 | #if WLR_HAS_DRM_BACKEND |
445 | } else if (wlr_output_is_drm(wlr_output)) { | ||
446 | tr = wlr_drm_connector_get_panel_orientation(wlr_output); | ||
447 | sway_log(SWAY_DEBUG, "Auto-detected output transform: %d", tr); | ||
448 | #endif | ||
449 | } | ||
450 | if (wlr_output->transform != tr) { | ||
451 | sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, tr); | ||
452 | wlr_output_state_set_transform(pending, tr); | ||
373 | } | 453 | } |
374 | 454 | ||
375 | // Apply the scale last before the commit, because the scale auto-detection | 455 | // Apply the scale last before the commit, because the scale auto-detection |
@@ -377,50 +457,50 @@ static void queue_output_config(struct output_config *oc, | |||
377 | float scale; | 457 | float scale; |
378 | if (oc && oc->scale > 0) { | 458 | if (oc && oc->scale > 0) { |
379 | scale = oc->scale; | 459 | scale = oc->scale; |
460 | |||
461 | // The factional-scale-v1 protocol uses increments of 120ths to send | ||
462 | // the scale factor to the client. Adjust the scale so that we use the | ||
463 | // same value as the clients'. | ||
464 | float adjusted_scale = round(scale * 120) / 120; | ||
465 | if (scale != adjusted_scale) { | ||
466 | sway_log(SWAY_INFO, "Adjusting output scale from %f to %f", | ||
467 | scale, adjusted_scale); | ||
468 | scale = adjusted_scale; | ||
469 | } | ||
380 | } else { | 470 | } else { |
381 | scale = compute_default_scale(wlr_output); | 471 | scale = compute_default_scale(wlr_output, pending); |
382 | sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale); | 472 | sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale); |
383 | } | 473 | } |
384 | if (scale != wlr_output->scale) { | 474 | if (scale != wlr_output->scale) { |
385 | sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale); | 475 | sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale); |
386 | wlr_output_set_scale(wlr_output, scale); | 476 | wlr_output_state_set_scale(pending, scale); |
387 | } | 477 | } |
388 | 478 | ||
389 | if (oc && oc->adaptive_sync != -1) { | 479 | if (oc && oc->adaptive_sync != -1) { |
390 | 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, |
391 | oc->adaptive_sync); | 481 | oc->adaptive_sync); |
392 | wlr_output_enable_adaptive_sync(wlr_output, oc->adaptive_sync == 1); | 482 | wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); |
393 | } | ||
394 | } | ||
395 | |||
396 | bool apply_output_config(struct output_config *oc, struct sway_output *output) { | ||
397 | if (output == root->noop_output) { | ||
398 | return false; | ||
399 | } | 483 | } |
400 | 484 | ||
401 | struct wlr_output *wlr_output = output->wlr_output; | 485 | if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { |
402 | 486 | if (oc->render_bit_depth == RENDER_BIT_DEPTH_10 && | |
403 | // Flag to prevent the output mode event handler from calling us | 487 | render_format_is_10bit(output->wlr_output->render_format)) { |
404 | output->enabling = (!oc || oc->enabled); | 488 | // 10-bit was set successfully before, try to save some tests by reusing the format |
405 | 489 | wlr_output_state_set_render_format(pending, output->wlr_output->render_format); | |
406 | queue_output_config(oc, output); | 490 | } else if (oc->render_bit_depth == RENDER_BIT_DEPTH_10) { |
407 | 491 | wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010); | |
408 | if (!oc || oc->dpms_state != DPMS_OFF) { | 492 | } else { |
409 | output->current_mode = wlr_output->pending.mode; | 493 | wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888); |
494 | } | ||
410 | } | 495 | } |
496 | } | ||
411 | 497 | ||
412 | sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name); | 498 | static bool finalize_output_config(struct output_config *oc, struct sway_output *output) { |
413 | if (!wlr_output_commit(wlr_output)) { | 499 | if (output == root->fallback_output) { |
414 | // Failed to commit output changes, maybe the output is missing a CRTC. | ||
415 | // Leave the output disabled for now and try again when the output gets | ||
416 | // the mode we asked for. | ||
417 | sway_log(SWAY_ERROR, "Failed to commit output %s", wlr_output->name); | ||
418 | output->enabling = false; | ||
419 | return false; | 500 | return false; |
420 | } | 501 | } |
421 | 502 | ||
422 | output->enabling = false; | 503 | struct wlr_output *wlr_output = output->wlr_output; |
423 | |||
424 | if (oc && !oc->enabled) { | 504 | if (oc && !oc->enabled) { |
425 | sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); | 505 | sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); |
426 | if (output->enabled) { | 506 | if (output->enabled) { |
@@ -430,10 +510,6 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
430 | return true; | 510 | return true; |
431 | } | 511 | } |
432 | 512 | ||
433 | if (config->reloading) { | ||
434 | output_damage_whole(output); | ||
435 | } | ||
436 | |||
437 | if (oc) { | 513 | if (oc) { |
438 | enum scale_filter_mode scale_filter_old = output->scale_filter; | 514 | enum scale_filter_mode scale_filter_old = output->scale_filter; |
439 | switch (oc->scale_filter) { | 515 | switch (oc->scale_filter) { |
@@ -450,6 +526,7 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
450 | if (scale_filter_old != output->scale_filter) { | 526 | if (scale_filter_old != output->scale_filter) { |
451 | sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, | 527 | sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, |
452 | sway_output_scale_filter_to_string(output->scale_filter)); | 528 | sway_output_scale_filter_to_string(output->scale_filter)); |
529 | wlr_damage_ring_add_whole(&output->scene_output->damage_ring); | ||
453 | } | 530 | } |
454 | } | 531 | } |
455 | 532 | ||
@@ -462,12 +539,12 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
462 | } | 539 | } |
463 | 540 | ||
464 | // Update output->{lx, ly, width, height} | 541 | // Update output->{lx, ly, width, height} |
465 | struct wlr_box *output_box = | 542 | struct wlr_box output_box; |
466 | wlr_output_layout_get_box(root->output_layout, wlr_output); | 543 | wlr_output_layout_get_box(root->output_layout, wlr_output, &output_box); |
467 | output->lx = output_box->x; | 544 | output->lx = output_box.x; |
468 | output->ly = output_box->y; | 545 | output->ly = output_box.y; |
469 | output->width = output_box->width; | 546 | output->width = output_box.width; |
470 | output->height = output_box->height; | 547 | output->height = output_box.height; |
471 | 548 | ||
472 | if (!output->enabled) { | 549 | if (!output->enabled) { |
473 | output_enable(output); | 550 | output_enable(output); |
@@ -479,27 +556,13 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { | |||
479 | output->max_render_time = oc->max_render_time; | 556 | output->max_render_time = oc->max_render_time; |
480 | } | 557 | } |
481 | 558 | ||
482 | // Reconfigure all devices, since input config may have been applied before | ||
483 | // this output came online, and some config items (like map_to_output) are | ||
484 | // dependent on an output being present. | ||
485 | input_manager_configure_all_inputs(); | ||
486 | return true; | 559 | return true; |
487 | } | 560 | } |
488 | 561 | ||
489 | bool test_output_config(struct output_config *oc, struct sway_output *output) { | ||
490 | if (output == root->noop_output) { | ||
491 | return false; | ||
492 | } | ||
493 | |||
494 | queue_output_config(oc, output); | ||
495 | bool ok = wlr_output_test(output->wlr_output); | ||
496 | wlr_output_rollback(output->wlr_output); | ||
497 | return ok; | ||
498 | } | ||
499 | |||
500 | static void default_output_config(struct output_config *oc, | 562 | static void default_output_config(struct output_config *oc, |
501 | struct wlr_output *wlr_output) { | 563 | struct wlr_output *wlr_output) { |
502 | oc->enabled = 1; | 564 | oc->enabled = 1; |
565 | oc->power = 1; | ||
503 | struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); | 566 | struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); |
504 | if (mode != NULL) { | 567 | if (mode != NULL) { |
505 | oc->width = mode->width; | 568 | oc->width = mode->width; |
@@ -512,147 +575,418 @@ static void default_output_config(struct output_config *oc, | |||
512 | struct sway_output *output = wlr_output->data; | 575 | struct sway_output *output = wlr_output->data; |
513 | oc->subpixel = output->detected_subpixel; | 576 | oc->subpixel = output->detected_subpixel; |
514 | oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; | 577 | oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; |
515 | oc->dpms_state = DPMS_ON; | ||
516 | oc->max_render_time = 0; | 578 | oc->max_render_time = 0; |
517 | } | 579 | } |
518 | 580 | ||
519 | static struct output_config *get_output_config(char *identifier, | 581 | // find_output_config returns a merged output_config containing all stored |
520 | 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) { | ||
521 | const char *name = sway_output->wlr_output->name; | 584 | const char *name = sway_output->wlr_output->name; |
585 | struct output_config *oc = NULL; | ||
586 | |||
587 | struct output_config *result = new_output_config(name); | ||
588 | if (config->reloading) { | ||
589 | default_output_config(result, sway_output->wlr_output); | ||
590 | } | ||
522 | 591 | ||
523 | struct output_config *oc_id_on_name = NULL; | 592 | char id[128]; |
524 | struct output_config *oc_name = NULL; | 593 | output_get_identifier(id, sizeof(id), sway_output); |
525 | struct output_config *oc_id = NULL; | ||
526 | 594 | ||
527 | size_t length = snprintf(NULL, 0, "%s on %s", identifier, name) + 1; | 595 | int i; |
528 | char *id_on_name = malloc(length); | 596 | bool match = false; |
529 | snprintf(id_on_name, length, "%s on %s", identifier, name); | 597 | if ((i = list_seq_find(config->output_configs, output_name_cmp, "*")) >= 0) { |
530 | int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); | 598 | match = true; |
531 | if (i >= 0) { | 599 | oc = config->output_configs->items[i]; |
532 | oc_id_on_name = config->output_configs->items[i]; | 600 | merge_output_config(result, oc); |
533 | } else { | 601 | } |
534 | i = list_seq_find(config->output_configs, output_name_cmp, name); | 602 | if ((i = list_seq_find(config->output_configs, output_name_cmp, name)) >= 0) { |
535 | if (i >= 0) { | 603 | match = true; |
536 | oc_name = config->output_configs->items[i]; | 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)" : ""); | ||
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) { | ||
678 | |||
679 | // Clear and disable all output states after this one to avoid conflict | ||
680 | // with previous tests. | ||
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; | ||
537 | } | 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; | ||
716 | } | ||
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 | } | ||
538 | 723 | ||
539 | i = list_seq_find(config->output_configs, output_name_cmp, identifier); | 724 | static bool search_mode(struct search_context *ctx, size_t output_idx) { |
540 | if (i >= 0) { | 725 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
541 | oc_id = config->output_configs->items[i]; | 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); | ||
732 | } | ||
733 | |||
734 | struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); | ||
735 | if (preferred_mode) { | ||
736 | wlr_output_state_set_mode(state, preferred_mode); | ||
737 | if (search_adaptive_sync(ctx, output_idx)) { | ||
738 | return true; | ||
542 | } | 739 | } |
543 | } | 740 | } |
544 | 741 | ||
545 | struct output_config *result = new_output_config("temp"); | 742 | if (wl_list_empty(&wlr_output->modes)) { |
546 | if (config->reloading) { | 743 | state->committed &= ~WLR_OUTPUT_STATE_MODE; |
547 | default_output_config(result, sway_output->wlr_output); | 744 | return search_adaptive_sync(ctx, output_idx); |
548 | } | 745 | } |
549 | if (oc_id_on_name) { | 746 | |
550 | // Already have an identifier on name config, use that | 747 | struct wlr_output_mode *mode; |
551 | free(result->name); | 748 | wl_list_for_each(mode, &backend_state->output->modes, link) { |
552 | result->name = strdup(id_on_name); | 749 | if (mode == preferred_mode) { |
553 | merge_output_config(result, oc_id_on_name); | 750 | continue; |
554 | } else if (oc_name && oc_id) { | 751 | } |
555 | // Generate a config named `<identifier> on <name>` which contains a | 752 | wlr_output_state_set_mode(state, mode); |
556 | // merged copy of the identifier on name. This will make sure that both | 753 | if (search_adaptive_sync(ctx, output_idx)) { |
557 | // identifier and name configs are respected, with identifier getting | 754 | return true; |
558 | // priority | ||
559 | struct output_config *temp = new_output_config(id_on_name); | ||
560 | merge_output_config(temp, oc_name); | ||
561 | merge_output_config(temp, oc_id); | ||
562 | list_add(config->output_configs, temp); | ||
563 | |||
564 | free(result->name); | ||
565 | result->name = strdup(id_on_name); | ||
566 | merge_output_config(result, temp); | ||
567 | |||
568 | sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" | ||
569 | " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" | ||
570 | " (dpms %d) (max render time: %d)", result->name, result->enabled, | ||
571 | result->width, result->height, result->refresh_rate, | ||
572 | result->x, result->y, result->scale, result->transform, | ||
573 | result->background, result->background_option, result->dpms_state, | ||
574 | result->max_render_time); | ||
575 | } else if (oc_name) { | ||
576 | // No identifier config, just return a copy of the name config | ||
577 | free(result->name); | ||
578 | result->name = strdup(name); | ||
579 | merge_output_config(result, oc_name); | ||
580 | } else if (oc_id) { | ||
581 | // No name config, just return a copy of the identifier config | ||
582 | free(result->name); | ||
583 | result->name = strdup(identifier); | ||
584 | merge_output_config(result, oc_id); | ||
585 | } else { | ||
586 | i = list_seq_find(config->output_configs, output_name_cmp, "*"); | ||
587 | if (i >= 0) { | ||
588 | // No name or identifier config, but there is a wildcard config | ||
589 | free(result->name); | ||
590 | result->name = strdup("*"); | ||
591 | merge_output_config(result, config->output_configs->items[i]); | ||
592 | } else if (!config->reloading) { | ||
593 | // No name, identifier, or wildcard config. Since we are not | ||
594 | // reloading with defaults, the output config will be empty, so | ||
595 | // just return NULL | ||
596 | free_output_config(result); | ||
597 | result = NULL; | ||
598 | } | 755 | } |
599 | } | 756 | } |
600 | 757 | ||
601 | free(id_on_name); | 758 | return false; |
602 | return result; | ||
603 | } | 759 | } |
604 | 760 | ||
605 | struct output_config *find_output_config(struct sway_output *output) { | 761 | static bool search_render_format(struct search_context *ctx, size_t output_idx) { |
606 | char id[128]; | 762 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
607 | output_get_identifier(id, sizeof(id), output); | 763 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; |
608 | 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; | ||
609 | } | 796 | } |
610 | 797 | ||
611 | void apply_output_config_to_outputs(struct output_config *oc) { | 798 | static bool search_valid_config(struct search_context *ctx, size_t output_idx) { |
612 | // Try to find the output container and apply configuration now. If | 799 | if (output_idx >= ctx->configs_len) { |
613 | // this is during startup then there will be no container and config | 800 | // We reached the end of the search, all good! |
614 | // will be applied during normal "new output" event from wlroots. | 801 | return true; |
615 | bool wildcard = strcmp(oc->name, "*") == 0; | 802 | } |
616 | char id[128]; | ||
617 | struct sway_output *sway_output, *tmp; | ||
618 | wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) { | ||
619 | char *name = sway_output->wlr_output->name; | ||
620 | output_get_identifier(id, sizeof(id), sway_output); | ||
621 | if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) { | ||
622 | struct output_config *current = get_output_config(id, sway_output); | ||
623 | if (!current) { | ||
624 | // No stored output config matched, apply oc directly | ||
625 | sway_log(SWAY_DEBUG, "Applying oc directly"); | ||
626 | current = new_output_config(oc->name); | ||
627 | merge_output_config(current, oc); | ||
628 | } | ||
629 | apply_output_config(current, sway_output); | ||
630 | free_output_config(current); | ||
631 | 803 | ||
632 | if (!wildcard) { | 804 | struct matched_output_config *cfg = &ctx->configs[output_idx]; |
633 | // Stop looking if the output config isn't applicable to all | 805 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; |
634 | // outputs | 806 | struct wlr_output_state *state = &backend_state->base; |
635 | break; | 807 | struct wlr_output *wlr_output = backend_state->output; |
636 | } | 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; | ||
897 | } | ||
898 | } | ||
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; | ||
637 | } | 919 | } |
638 | } | 920 | } |
639 | 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 | |||
640 | struct sway_seat *seat; | 954 | struct sway_seat *seat; |
641 | wl_list_for_each(seat, &server.input->seats, link) { | 955 | wl_list_for_each(seat, &server.input->seats, link) { |
642 | wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); | 956 | wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); |
643 | cursor_rebase(seat->cursor); | 957 | cursor_rebase(seat->cursor); |
644 | } | 958 | } |
959 | |||
960 | return ok; | ||
645 | } | 961 | } |
646 | 962 | ||
647 | void reset_outputs(void) { | 963 | void apply_all_output_configs(void) { |
648 | struct output_config *oc = NULL; | 964 | size_t configs_len = wl_list_length(&root->all_outputs); |
649 | int i = list_seq_find(config->output_configs, output_name_cmp, "*"); | 965 | struct matched_output_config *configs = calloc(configs_len, sizeof(*configs)); |
650 | if (i >= 0) { | 966 | if (!configs) { |
651 | oc = config->output_configs->items[i]; | 967 | return; |
652 | } else { | ||
653 | oc = store_output_config(new_output_config("*")); | ||
654 | } | 968 | } |
655 | apply_output_config_to_outputs(oc); | 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); | ||
988 | } | ||
989 | free(configs); | ||
656 | } | 990 | } |
657 | 991 | ||
658 | void free_output_config(struct output_config *oc) { | 992 | void free_output_config(struct output_config *oc) { |
@@ -702,6 +1036,8 @@ static bool _spawn_swaybg(char **command) { | |||
702 | sway_log_errno(SWAY_ERROR, "fork failed"); | 1036 | sway_log_errno(SWAY_ERROR, "fork failed"); |
703 | return false; | 1037 | return false; |
704 | } else if (pid == 0) { | 1038 | } else if (pid == 0) { |
1039 | restore_nofile_limit(); | ||
1040 | |||
705 | pid = fork(); | 1041 | pid = fork(); |
706 | if (pid < 0) { | 1042 | if (pid < 0) { |
707 | sway_log_errno(SWAY_ERROR, "fork failed"); | 1043 | sway_log_errno(SWAY_ERROR, "fork failed"); |
@@ -717,7 +1053,9 @@ static bool _spawn_swaybg(char **command) { | |||
717 | setenv("WAYLAND_SOCKET", wayland_socket_str, true); | 1053 | setenv("WAYLAND_SOCKET", wayland_socket_str, true); |
718 | 1054 | ||
719 | execvp(command[0], command); | 1055 | execvp(command[0], command); |
720 | sway_log_errno(SWAY_ERROR, "execvp failed"); | 1056 | sway_log_errno(SWAY_ERROR, "failed to execute '%s' " |
1057 | "(background configuration probably not applied)", | ||
1058 | command[0]); | ||
721 | _exit(EXIT_FAILURE); | 1059 | _exit(EXIT_FAILURE); |
722 | } | 1060 | } |
723 | _exit(EXIT_SUCCESS); | 1061 | _exit(EXIT_SUCCESS); |
@@ -727,12 +1065,13 @@ static bool _spawn_swaybg(char **command) { | |||
727 | sway_log_errno(SWAY_ERROR, "close failed"); | 1065 | sway_log_errno(SWAY_ERROR, "close failed"); |
728 | return false; | 1066 | return false; |
729 | } | 1067 | } |
730 | if (waitpid(pid, NULL, 0) < 0) { | 1068 | int fork_status = 0; |
1069 | if (waitpid(pid, &fork_status, 0) < 0) { | ||
731 | sway_log_errno(SWAY_ERROR, "waitpid failed"); | 1070 | sway_log_errno(SWAY_ERROR, "waitpid failed"); |
732 | return false; | 1071 | return false; |
733 | } | 1072 | } |
734 | 1073 | ||
735 | return true; | 1074 | return WIFEXITED(fork_status) && WEXITSTATUS(fork_status) == EXIT_SUCCESS; |
736 | } | 1075 | } |
737 | 1076 | ||
738 | bool spawn_swaybg(void) { | 1077 | bool spawn_swaybg(void) { |