monitor-config-store: Attempt to convert physical to logical layout modes
Introduce some "best effort" conversion code to migrate monitor configurations from PHYSICAL (the old default) to LOGICAL (the new default on wayland) layout mode. This conversion will only be used when the old PHYSICAL layout-mode configuration is not compatible with the new LOGICAL layout-mode one. This only applies if 1) there's a monitor that needs scaling in the layout, and 2) the scaled monitor comes before other monitors in the coordinate system (ie. it's not the rightmost or bottommost monitor). There are two algorithms added here to convert monitor layouts: - One for "simple" 1-dimensional monitor layouts, where all monitors are aligned on a vertical or horizontal strip. Here's a few (inaccurate) examples of how this would look with different layouts (left side is PHYSICAL, right side is LOGICAL, x is the origin of the coordinate system, the numbers are scales of the monitors): ``` x──────┬──────┬──────┐ x ┌──────┐ │ 2 │ 1 │ 2 │ 2┌──┤ 1 ├──┐2 │ │ │ │ ──► └──┤ ├──┘ └──────┴──────┴──────┘ └──────┘ x ┌──────┐ x ┌──────┐ ┌────┤ 1 │ ┌──┤ 1 │ │ 2 │ │ ──► └──┤ │ └────┴──────┘ 2 └──────┘ x ┌────┐ ┌──────┤ │ x──────┐ │ │ │ │ ├─┬────┐ │ 1 │ 3 │ ──► │ 1 │3│ 1 │ │ │ │ │ ├─┴────┘ └──────┤ │ └──────┘ │ ├────┐ │ │ 1 │ └────┴────┘ ``` - A second more complex algorithm for 2-dimensional monitor layouts with a common baseline that all monitors are aligned to. And examples for this one: ``` x ┌──────┐ ┌──────┤ │ │ 1 │ 2 │ x──────┐ │ │ │ │ 1 ├────┐ └──┬───┴───┬──┘ ──► │ │ 2 │ │ 3 │ ├──┬───┴────┘ │ │ └──┘3 └───────┘ x ┌──────┬──────┐ │ 1 │ │ │ │ │ x──────┬──────┐ ┌─────┴──────┤ 1 │ │ 1 │ │ │ │ │ │ │ │ │ │ │ ──► └──┬───┤ 1 │ │ 3 │ │ │ 3 │ │ │ ├──────┘ └───┤ │ │ │ │ │ │ │ └──────┘ └────────────┘ x ┌───────┐ ┌──────┐ │ │ x ┌───────┐ │ 2 │ ┌──────┤ 1 │ │ │ │ │ │ 1 │ │ 2 ┌──────┤ 1 │ └────┬─┴────┴─┬────┴───────┘ ──► ┌──┤ 1 │ │ │ │ ├──┴┬─────┴───────┘ │ 2 │ │ 2 │ │ │ └───┘ └────────┘ ``` These algorithms will fail for any more complex 2d monitor layout, eg. ``` x ┌───┬────┐ │ 2 │ 1 │ │ ├────┘ ┌───┴┬──┘ │ 1 │ └────┘ x───┬───┬───┐ │ 1 │ 2 │ 1 │ ├───┼───┼───┤ │ 1 │ 1 │ 1 │ ├───┼───┼───┤ │ 1 │ 1 │ 1 │ └───┴───┴───┘ ``` In those cases where the conversion failed, we fall back to aligning the monitors on a horizontal line, preserving the scale, the primary monitor and the disabled monitors. Note that we also need to convert the scale factor in some cases, because LOGICAL layout mode also behaves different here: When the scale results in a fractional logical monitor size (eg. the native monitor width is 2560px, and a scale of 3 is set => 2560px / 3 = 853.333px), in LOGICAL mode we won't use that scale. Instead we have an algorithm (see meta_monitor_get_closest_scale_factor_for_resolution()) to find the nearest fractional scale factor which doesn't result in fractional logical monitor size. We reuse this algorithm here for the conversion. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3596>
This commit is contained in:
parent
96791f111a
commit
2c8b383a7e
@ -781,6 +781,492 @@ detect_layout_mode_configs (MetaMonitorManager *monitor_manager,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
maybe_convert_scales (GList *logical_monitor_configs)
|
||||
{
|
||||
GList *l;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
unsigned int width, height;
|
||||
float existing_scale = logical_monitor_config->scale;
|
||||
float existing_scaled_width, existing_scaled_height;
|
||||
float new_scale = 0.0f;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config, &width, &height);
|
||||
|
||||
existing_scaled_width = width / existing_scale;
|
||||
existing_scaled_height = height / existing_scale;
|
||||
|
||||
if (floorf (existing_scaled_width) == existing_scaled_width &&
|
||||
floorf (existing_scaled_height) == existing_scaled_height)
|
||||
continue;
|
||||
|
||||
new_scale =
|
||||
meta_get_closest_monitor_scale_factor_for_resolution (width,
|
||||
height,
|
||||
existing_scale,
|
||||
0.1f);
|
||||
if (new_scale == 0.0f)
|
||||
new_scale = 1.0f;
|
||||
|
||||
logical_monitor_config->scale = new_scale;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
try_convert_1_dimensional_line (GList *logical_monitor_configs,
|
||||
gboolean horizontal)
|
||||
{
|
||||
int i;
|
||||
unsigned int n_monitors = g_list_length (logical_monitor_configs);
|
||||
unsigned int n_monitors_found;
|
||||
unsigned int looking_for;
|
||||
unsigned int accumulated;
|
||||
MetaLogicalMonitorConfig *prev_logical_monitor_config;
|
||||
|
||||
/* Before we change any values, make sure monitors are actually aligned on a
|
||||
* straight line.
|
||||
*/
|
||||
looking_for = 0;
|
||||
n_monitors_found = 0;
|
||||
for (i = 0; i < n_monitors; i++)
|
||||
{
|
||||
GList *l;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
unsigned int width, height;
|
||||
|
||||
if ((horizontal && logical_monitor_config->layout.x != looking_for) ||
|
||||
(!horizontal && logical_monitor_config->layout.y != looking_for))
|
||||
continue;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config, &width, &height);
|
||||
|
||||
looking_for += horizontal ? width : height;
|
||||
|
||||
n_monitors_found++;
|
||||
}
|
||||
}
|
||||
|
||||
if (n_monitors_found != n_monitors)
|
||||
{
|
||||
/* If we haven't found all the monitors on our straight line, we can't
|
||||
* run the algorithm.
|
||||
*/
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
looking_for = 0;
|
||||
accumulated = 0;
|
||||
prev_logical_monitor_config = NULL;
|
||||
for (i = 0; i < n_monitors; i++)
|
||||
{
|
||||
GList *l;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
unsigned int width, height;
|
||||
float scale = logical_monitor_config->scale;
|
||||
|
||||
if ((horizontal && logical_monitor_config->layout.x != looking_for) ||
|
||||
(!horizontal && logical_monitor_config->layout.y != looking_for))
|
||||
continue;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config, &width, &height);
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
logical_monitor_config->layout.x = accumulated;
|
||||
|
||||
/* In the other dimension, always center in relation to the previous
|
||||
* monitor.
|
||||
*/
|
||||
if (prev_logical_monitor_config)
|
||||
{
|
||||
unsigned int prev_width, prev_height;
|
||||
float centerline;
|
||||
|
||||
get_monitor_size_with_rotation (prev_logical_monitor_config,
|
||||
&prev_width, &prev_height);
|
||||
|
||||
centerline = prev_logical_monitor_config->layout.y +
|
||||
(int) roundf ((prev_height / prev_logical_monitor_config->scale) / 2.0f);
|
||||
|
||||
logical_monitor_config->layout.y =
|
||||
(int) (centerline - roundf ((height / scale) / 2.0f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logical_monitor_config->layout.y = accumulated;
|
||||
|
||||
/* See comment above */
|
||||
if (prev_logical_monitor_config)
|
||||
{
|
||||
unsigned int prev_width, prev_height;
|
||||
float centerline;
|
||||
|
||||
get_monitor_size_with_rotation (prev_logical_monitor_config,
|
||||
&prev_width, &prev_height);
|
||||
|
||||
centerline = prev_logical_monitor_config->layout.x +
|
||||
roundf ((prev_width / prev_logical_monitor_config->scale) / 2.0f);
|
||||
|
||||
logical_monitor_config->layout.x =
|
||||
(int) (centerline - roundf ((width / scale) / 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
looking_for += horizontal ? width : height;
|
||||
accumulated += (int) roundf ((horizontal ? width : height) / scale);
|
||||
|
||||
prev_logical_monitor_config = logical_monitor_config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
try_convert_2d_with_baseline (GList *logical_monitor_configs,
|
||||
gboolean horizontal)
|
||||
{
|
||||
/* Look for a shared baseline which every monitor is aligned to,
|
||||
* then calculate the new layout keeping that baseline.
|
||||
*
|
||||
* This one consists of a lot of steps, to make explanations easier,
|
||||
* we'll assume a horizontal baseline for all explanations in comments.
|
||||
*/
|
||||
|
||||
int i;
|
||||
unsigned int n_monitors = g_list_length (logical_monitor_configs);
|
||||
MetaLogicalMonitorConfig *first_logical_monitor_config =
|
||||
logical_monitor_configs->data;
|
||||
unsigned int width, height;
|
||||
unsigned int looking_for_1, looking_for_2;
|
||||
gboolean baseline_is_1, baseline_is_2;
|
||||
GList *l;
|
||||
unsigned int baseline;
|
||||
unsigned int cur_side_1, cur_side_2;
|
||||
|
||||
get_monitor_size_with_rotation (first_logical_monitor_config,
|
||||
&width, &height);
|
||||
|
||||
/* Step 1: We don't know whether the first monitor is above or below the
|
||||
* baseline, so there are two possible baselines: Top or bottom edge of
|
||||
* the first monitor.
|
||||
*
|
||||
* Find out which one the actual baseline is, top or bottom edge!
|
||||
*/
|
||||
|
||||
looking_for_1 = horizontal
|
||||
? first_logical_monitor_config->layout.y
|
||||
: first_logical_monitor_config->layout.x;
|
||||
looking_for_2 = horizontal
|
||||
? first_logical_monitor_config->layout.y + height
|
||||
: first_logical_monitor_config->layout.x + width;
|
||||
|
||||
baseline_is_1 = baseline_is_2 = TRUE;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config,
|
||||
&width, &height);
|
||||
|
||||
if ((horizontal &&
|
||||
logical_monitor_config->layout.y != looking_for_1 &&
|
||||
logical_monitor_config->layout.y + height != looking_for_1) ||
|
||||
(!horizontal &&
|
||||
logical_monitor_config->layout.x != looking_for_1 &&
|
||||
logical_monitor_config->layout.x + width != looking_for_1))
|
||||
baseline_is_1 = FALSE;
|
||||
|
||||
if ((horizontal &&
|
||||
logical_monitor_config->layout.y != looking_for_2 &&
|
||||
logical_monitor_config->layout.y + height != looking_for_2) ||
|
||||
(!horizontal &&
|
||||
logical_monitor_config->layout.x != looking_for_2 &&
|
||||
logical_monitor_config->layout.x + width != looking_for_2))
|
||||
baseline_is_2 = FALSE;
|
||||
}
|
||||
|
||||
if (!baseline_is_1 && !baseline_is_2)
|
||||
{
|
||||
/* We couldn't find a clear baseline which all monitors are aligned with,
|
||||
* this conversion won't work!
|
||||
*/
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
baseline = baseline_is_1 ? looking_for_1 : looking_for_2;
|
||||
|
||||
/* Step 2: Now that we have a baseline, go through the monitors
|
||||
* above the baseline which need to be scaled, and move their top
|
||||
* edge so that their bottom edge is still aligned with the baseline.
|
||||
*
|
||||
* For the monitors below the baseline there's no such need, because
|
||||
* even with scale, their top edge will remain aligned with the
|
||||
* baseline.
|
||||
*/
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
|
||||
if (logical_monitor_config->scale == 1.0f)
|
||||
continue;
|
||||
|
||||
/* Filter out all the monitors below the baseline */
|
||||
if ((horizontal && logical_monitor_config->layout.y == baseline) ||
|
||||
(!horizontal && logical_monitor_config->layout.x == baseline))
|
||||
continue;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config,
|
||||
&width, &height);
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
logical_monitor_config->layout.y =
|
||||
baseline - (int) roundf (height / logical_monitor_config->scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
logical_monitor_config->layout.x =
|
||||
baseline - (int) roundf (width / logical_monitor_config->scale);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 3: Still not done... Now we're done aligning monitors with the
|
||||
* baseline, but the scaling might also have opened holes in the horizontal
|
||||
* direction.
|
||||
*
|
||||
* We need to "walk along" the monitor strips above and below the baseline
|
||||
* and make sure everything is adjacent on both sides of the baseline.
|
||||
*/
|
||||
|
||||
cur_side_1 = 0;
|
||||
cur_side_2 = 0;
|
||||
|
||||
for (i = 0; i < n_monitors; i++)
|
||||
{
|
||||
unsigned int min_side_1 = G_MAXUINT;
|
||||
unsigned int min_side_2 = G_MAXUINT;
|
||||
MetaLogicalMonitorConfig *lowest_mon_side_1 = NULL;
|
||||
MetaLogicalMonitorConfig *lowest_mon_side_2 = NULL;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
|
||||
if ((horizontal && logical_monitor_config->layout.y != baseline) ||
|
||||
(!horizontal && logical_monitor_config->layout.x != baseline))
|
||||
{
|
||||
/* above the baseline */
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
if (logical_monitor_config->layout.x >= cur_side_1 &&
|
||||
logical_monitor_config->layout.x < min_side_1)
|
||||
{
|
||||
min_side_1 = logical_monitor_config->layout.x;
|
||||
lowest_mon_side_1 = logical_monitor_config;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logical_monitor_config->layout.y >= cur_side_1 &&
|
||||
logical_monitor_config->layout.y < min_side_1)
|
||||
{
|
||||
min_side_1 = logical_monitor_config->layout.y;
|
||||
lowest_mon_side_1 = logical_monitor_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* below the baseline */
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
if (logical_monitor_config->layout.x >= cur_side_2 &&
|
||||
logical_monitor_config->layout.x < min_side_2)
|
||||
{
|
||||
min_side_2 = logical_monitor_config->layout.x;
|
||||
lowest_mon_side_2 = logical_monitor_config;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logical_monitor_config->layout.y >= cur_side_2 &&
|
||||
logical_monitor_config->layout.y < min_side_2)
|
||||
{
|
||||
min_side_2 = logical_monitor_config->layout.y;
|
||||
lowest_mon_side_2 = logical_monitor_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lowest_mon_side_1)
|
||||
{
|
||||
get_monitor_size_with_rotation (lowest_mon_side_1, &width, &height);
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
lowest_mon_side_1->layout.x = cur_side_1;
|
||||
cur_side_1 += (int) roundf (width / lowest_mon_side_1->scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_mon_side_1->layout.y = cur_side_1;
|
||||
cur_side_1 += (int) roundf (height / lowest_mon_side_1->scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (lowest_mon_side_2)
|
||||
{
|
||||
get_monitor_size_with_rotation (lowest_mon_side_2, &width, &height);
|
||||
|
||||
if (horizontal)
|
||||
{
|
||||
lowest_mon_side_2->layout.x = cur_side_2;
|
||||
cur_side_2 += (int) roundf (width / lowest_mon_side_2->scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_mon_side_2->layout.y = cur_side_2;
|
||||
cur_side_2 += (int) roundf (height / lowest_mon_side_2->scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
convert_align_on_horizontal_line (GList *logical_monitor_configs)
|
||||
{
|
||||
GList *l;
|
||||
unsigned int accumulated_x = 0;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
unsigned int width, height;
|
||||
|
||||
get_monitor_size_with_rotation (logical_monitor_config, &width, &height);
|
||||
|
||||
logical_monitor_config->layout.x = accumulated_x;
|
||||
logical_monitor_config->layout.y = 0;
|
||||
|
||||
accumulated_x += (int) roundf (width / logical_monitor_config->scale);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
adjust_for_offset (GList *logical_monitor_configs)
|
||||
{
|
||||
GList *l;
|
||||
int offset_x, offset_y;
|
||||
|
||||
offset_x = offset_y = G_MAXINT;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
|
||||
offset_x = MIN (offset_x, logical_monitor_config->layout.x);
|
||||
offset_y = MIN (offset_y, logical_monitor_config->layout.y);
|
||||
}
|
||||
|
||||
if (offset_x == G_MAXINT && offset_y == G_MAXINT)
|
||||
return;
|
||||
|
||||
for (l = logical_monitor_configs; l; l = l->next)
|
||||
{
|
||||
MetaLogicalMonitorConfig *logical_monitor_config = l->data;
|
||||
|
||||
if (offset_x != G_MAXINT)
|
||||
logical_monitor_config->layout.x -= offset_x;
|
||||
|
||||
if (offset_y != G_MAXINT)
|
||||
logical_monitor_config->layout.y -= offset_y;
|
||||
}
|
||||
}
|
||||
|
||||
static MetaMonitorsConfig *
|
||||
attempt_layout_mode_conversion (MetaMonitorManager *monitor_manager,
|
||||
GList *logical_monitor_configs,
|
||||
GList *disabled_monitor_specs,
|
||||
MetaMonitorsConfigFlag config_flags)
|
||||
{
|
||||
GList *logical_monitor_configs_copy;
|
||||
MetaMonitorsConfig *new_logical_config;
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
|
||||
logical_monitor_configs_copy =
|
||||
meta_clone_logical_monitor_config_list (logical_monitor_configs);
|
||||
|
||||
maybe_convert_scales (logical_monitor_configs_copy);
|
||||
derive_logical_monitor_layouts (logical_monitor_configs_copy,
|
||||
META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL);
|
||||
|
||||
if (meta_verify_logical_monitor_config_list (logical_monitor_configs,
|
||||
META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL,
|
||||
monitor_manager,
|
||||
&local_error))
|
||||
{
|
||||
/* Great, it was enough to convert the scales and the config is now already
|
||||
* valid in LOGICAL mode, can skip the fallible conversion paths.
|
||||
*/
|
||||
goto create_full_config;
|
||||
}
|
||||
|
||||
if (!try_convert_1_dimensional_line (logical_monitor_configs_copy, TRUE) &&
|
||||
!try_convert_1_dimensional_line (logical_monitor_configs_copy, FALSE) &&
|
||||
!try_convert_2d_with_baseline (logical_monitor_configs_copy, TRUE) &&
|
||||
!try_convert_2d_with_baseline (logical_monitor_configs_copy, FALSE))
|
||||
{
|
||||
/* All algorithms we have to convert failed, this is expected for complex
|
||||
* layouts, so fall back to the simple method and align all monitors on
|
||||
* a horizontal line.
|
||||
*/
|
||||
convert_align_on_horizontal_line (logical_monitor_configs_copy);
|
||||
}
|
||||
|
||||
adjust_for_offset (logical_monitor_configs_copy);
|
||||
|
||||
create_full_config:
|
||||
new_logical_config =
|
||||
meta_monitors_config_new_full (g_steal_pointer (&logical_monitor_configs_copy),
|
||||
g_list_copy_deep (disabled_monitor_specs,
|
||||
(GCopyFunc) meta_monitor_spec_clone,
|
||||
NULL),
|
||||
META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL,
|
||||
config_flags);
|
||||
|
||||
if (!meta_verify_monitors_config (new_logical_config, monitor_manager, &local_error))
|
||||
{
|
||||
/* Verification of the converted config failed, this should not happen as the
|
||||
* conversion functions should give up in case conversion is not possible.
|
||||
*/
|
||||
g_warning ("Verification of converted monitor config failed: %s",
|
||||
local_error->message);
|
||||
g_object_unref (new_logical_config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new_logical_config;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_end_element (GMarkupParseContext *context,
|
||||
const char *element_name,
|
||||
@ -989,6 +1475,19 @@ handle_end_element (GMarkupParseContext *context,
|
||||
g_hash_table_replace (parser->pending_configs,
|
||||
physical_layout_mode_config->key,
|
||||
physical_layout_mode_config);
|
||||
|
||||
/* If the config only works with PHYSICAL layout mode, we'll attempt to
|
||||
* convert the PHYSICAL config to LOGICAL. This will fail for
|
||||
* more complex configurations though.
|
||||
*/
|
||||
if (!logical_layout_mode_config)
|
||||
{
|
||||
logical_layout_mode_config =
|
||||
attempt_layout_mode_conversion (store->monitor_manager,
|
||||
physical_layout_mode_config->logical_monitor_configs,
|
||||
physical_layout_mode_config->disabled_monitor_specs,
|
||||
config_flags);
|
||||
}
|
||||
}
|
||||
|
||||
if (logical_layout_mode_config)
|
||||
|
Loading…
x
Reference in New Issue
Block a user