-
-
Notifications
You must be signed in to change notification settings - Fork 18
Introduce Monitor Layout Manager #423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
I think @jeremypw has some experience in this codebase? |
Its been quite a while since I looked at this plug but I'll try to test this out this weekend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some simplifications suggestions.
@@ -30,3 +30,5 @@ config_file = configure_file( | |||
subdir('data') | |||
subdir('src') | |||
subdir('po') | |||
|
|||
gnome.post_install(glib_compile_schemas: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meson_version should be set to '>=0.57' so that it doesn't try to run the build script in a older version.
public void save_layout (Gee.LinkedList<VirtualMonitor> virtual_monitors) { | ||
var key = get_layout_key (virtual_monitors); | ||
var layout_variant = build_layout_variant (virtual_monitors); | ||
|
||
var layouts = settings.get_value (PREFERRED_MONITOR_LAYOUTS_KEY); | ||
|
||
add_or_update_layout (layouts, key, layout_variant); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build_layout_variant and add_or_update_layout should be inlined here.
} | ||
|
||
public MonitorLayoutProfile? find_match_layout (string key) { | ||
// Layouts format are 'a{sa{sa{sv}}}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"a{sa{s(iiu)}}" should be simpler, we don't need the monitor properties be extensible, nor the extra boxing of using variant values.
var dict_builder = new VariantBuilder (VariantType.DICTIONARY); | ||
|
||
foreach (var monitor in virtual_monitors) { | ||
var props_builder = new VariantBuilder (VariantType.DICTIONARY); | ||
var key = monitor.monitors.get (0).hash.to_string (); | ||
|
||
var coordinate_x_variant = new Variant.variant (new Variant.int32 (monitor.x)); | ||
var coordinate_y_variant = new Variant.variant (new Variant.int32 (monitor.y)); | ||
var transform_variant = new Variant.variant (new Variant.int32 (monitor.transform)); | ||
|
||
props_builder.add_value (new Variant.dict_entry ("x", coordinate_x_variant)); | ||
props_builder.add_value (new Variant.dict_entry ("y", coordinate_y_variant)); | ||
props_builder.add_value (new Variant.dict_entry ("transform", transform_variant)); | ||
|
||
var props_variant = props_builder.end (); | ||
|
||
warning (props_variant.print (true)); | ||
|
||
dict_builder.add_value (new Variant.dict_entry (key, props_variant)); | ||
} | ||
|
||
return dict_builder.end (); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using VariantDict here make the code simple. Also, you can use monitor.id instead of getting the id of the first monitor only.
var dict_builder = new VariantBuilder (VariantType.DICTIONARY); | |
foreach (var monitor in virtual_monitors) { | |
var props_builder = new VariantBuilder (VariantType.DICTIONARY); | |
var key = monitor.monitors.get (0).hash.to_string (); | |
var coordinate_x_variant = new Variant.variant (new Variant.int32 (monitor.x)); | |
var coordinate_y_variant = new Variant.variant (new Variant.int32 (monitor.y)); | |
var transform_variant = new Variant.variant (new Variant.int32 (monitor.transform)); | |
props_builder.add_value (new Variant.dict_entry ("x", coordinate_x_variant)); | |
props_builder.add_value (new Variant.dict_entry ("y", coordinate_y_variant)); | |
props_builder.add_value (new Variant.dict_entry ("transform", transform_variant)); | |
var props_variant = props_builder.end (); | |
warning (props_variant.print (true)); | |
dict_builder.add_value (new Variant.dict_entry (key, props_variant)); | |
} | |
return dict_builder.end (); | |
var dict_builder = new VariantDict (); | |
foreach (var monitor in virtual_monitors) { | |
var props_builder = new VariantDict (); | |
props_builder.insert ("x", "v", new Variant.int32 (monitor.x)); | |
props_builder.insert ("y", "v", new Variant.int32 (monitor.y)); | |
props_builder.insert ("transform", "v", new Variant.uint32 (monitor.transform)); | |
var props_variant = props_builder.end (); | |
debug (props_variant.print (true)); | |
dict_builder.insert_value (monitor.id, props_variant)); | |
} | |
return dict_builder.end (); |
or using "{s(iiu)}" instead of "{sv}"
var dict_builder = new VariantBuilder (VariantType.DICTIONARY); | |
foreach (var monitor in virtual_monitors) { | |
var props_builder = new VariantBuilder (VariantType.DICTIONARY); | |
var key = monitor.monitors.get (0).hash.to_string (); | |
var coordinate_x_variant = new Variant.variant (new Variant.int32 (monitor.x)); | |
var coordinate_y_variant = new Variant.variant (new Variant.int32 (monitor.y)); | |
var transform_variant = new Variant.variant (new Variant.int32 (monitor.transform)); | |
props_builder.add_value (new Variant.dict_entry ("x", coordinate_x_variant)); | |
props_builder.add_value (new Variant.dict_entry ("y", coordinate_y_variant)); | |
props_builder.add_value (new Variant.dict_entry ("transform", transform_variant)); | |
var props_variant = props_builder.end (); | |
warning (props_variant.print (true)); | |
dict_builder.add_value (new Variant.dict_entry (key, props_variant)); | |
} | |
return dict_builder.end (); | |
var dict_builder = new VariantDict (); | |
foreach (var monitor in virtual_monitors) { | |
dict_builder.insert (monitor.id, "(iiu)", monitor.x, monitor.y, monitor.transform); | |
} | |
return dict_builder.end (); |
var layout_builder = new VariantBuilder (VariantType.DICTIONARY); | ||
bool found = false; | ||
|
||
for (var i = 0; i < layouts.n_children (); i++) { | ||
var layout = layouts.get_child_value (i); | ||
var layout_key = layout.get_child_value (0).get_string (); | ||
|
||
if (layout_key == key) { | ||
// Update existing layout | ||
layout_builder.add_value (new Variant.dict_entry (key, layout_variant)); | ||
found = true; | ||
} else { | ||
// Keep existing layout | ||
layout_builder.add_value (new Variant.dict_entry (layout_key, layout)); | ||
} | ||
} | ||
|
||
if (!found) { | ||
// Add new layout | ||
layout_builder.add_value (new Variant.dict_entry (key, layout_variant)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
var layout_builder = new VariantBuilder (VariantType.DICTIONARY); | |
bool found = false; | |
for (var i = 0; i < layouts.n_children (); i++) { | |
var layout = layouts.get_child_value (i); | |
var layout_key = layout.get_child_value (0).get_string (); | |
if (layout_key == key) { | |
// Update existing layout | |
layout_builder.add_value (new Variant.dict_entry (key, layout_variant)); | |
found = true; | |
} else { | |
// Keep existing layout | |
layout_builder.add_value (new Variant.dict_entry (layout_key, layout)); | |
} | |
} | |
if (!found) { | |
// Add new layout | |
layout_builder.add_value (new Variant.dict_entry (key, layout_variant)); | |
} | |
var layout_builder = new VariantDict (layouts); | |
layout_builder.insert_value (key, layout_variant); |
} | ||
|
||
public void arrange_monitors (Gee.LinkedList<VirtualMonitor> virtual_monitors) { | ||
if (virtual_monitors.size == 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cloned monitors have just one virtual monitor, so the checks bellow aren't necessary.
} | ||
|
||
var layout_key = get_layout_key (virtual_monitors); | ||
var layout = find_match_layout (layout_key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_match_layout should be inlined.
var has_update = false; | ||
|
||
if (layout != null) { | ||
foreach (var virtual_monitor in virtual_monitors) { | ||
var monitor_position = layout | ||
.find_position_by_id (virtual_monitor.monitors[0].hash.to_string ()); | ||
|
||
if (monitor_position != null) { | ||
if ((virtual_monitor.x != monitor_position.x | ||
|| virtual_monitor.y != monitor_position.y | ||
|| virtual_monitor.transform != monitor_position.transform) | ||
&& !is_cloned) { | ||
has_update = true; | ||
break; | ||
} | ||
|
||
virtual_monitor.x = monitor_position.x; | ||
virtual_monitor.y = monitor_position.y; | ||
virtual_monitor.transform = monitor_position.transform; | ||
} | ||
} | ||
} else { | ||
// If no layout found, we save the current layout to use later | ||
save_layout (virtual_monitors); | ||
} | ||
|
||
if (has_update) { | ||
// If the layout has been updated, save the new layout | ||
save_layout (virtual_monitors); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With MonitorManager.get_monitor_config() calling save_layout, and MonitorManager.disable_clone_mode() calling arrange_monitors, there's no need for handling updating the layout here.
The suggestion is assuming the above and that we are receiving the monitors dictionary from find_match_layout.
var has_update = false; | |
if (layout != null) { | |
foreach (var virtual_monitor in virtual_monitors) { | |
var monitor_position = layout | |
.find_position_by_id (virtual_monitor.monitors[0].hash.to_string ()); | |
if (monitor_position != null) { | |
if ((virtual_monitor.x != monitor_position.x | |
|| virtual_monitor.y != monitor_position.y | |
|| virtual_monitor.transform != monitor_position.transform) | |
&& !is_cloned) { | |
has_update = true; | |
break; | |
} | |
virtual_monitor.x = monitor_position.x; | |
virtual_monitor.y = monitor_position.y; | |
virtual_monitor.transform = monitor_position.transform; | |
} | |
} | |
} else { | |
// If no layout found, we save the current layout to use later | |
save_layout (virtual_monitors); | |
} | |
if (has_update) { | |
// If the layout has been updated, save the new layout | |
save_layout (virtual_monitors); | |
} | |
if (layout != null) { | |
foreach (var virtual_monitor in virtual_monitors) { | |
VariantDict monitor_position; | |
if (layout.lookup (virtual_monitor.id, "a{sv}", out monitor_position)) { | |
virtual_monitor.x = monitor_position.lookup_value ("x").get_int (); | |
virtual_monitor.y = monitor_position.lookup_value ("y").get_int (); | |
virtual_monitor.transform = monitor_position.lookup_value ("transform").get_uint (); | |
} | |
} | |
} else { | |
// If no layout found, we save the current layout to use later | |
save_layout (virtual_monitors); | |
} |
or with "{s(iiu)}" instead of "{sv}"
var has_update = false; | |
if (layout != null) { | |
foreach (var virtual_monitor in virtual_monitors) { | |
var monitor_position = layout | |
.find_position_by_id (virtual_monitor.monitors[0].hash.to_string ()); | |
if (monitor_position != null) { | |
if ((virtual_monitor.x != monitor_position.x | |
|| virtual_monitor.y != monitor_position.y | |
|| virtual_monitor.transform != monitor_position.transform) | |
&& !is_cloned) { | |
has_update = true; | |
break; | |
} | |
virtual_monitor.x = monitor_position.x; | |
virtual_monitor.y = monitor_position.y; | |
virtual_monitor.transform = monitor_position.transform; | |
} | |
} | |
} else { | |
// If no layout found, we save the current layout to use later | |
save_layout (virtual_monitors); | |
} | |
if (has_update) { | |
// If the layout has been updated, save the new layout | |
save_layout (virtual_monitors); | |
} | |
if (layout != null) { | |
foreach (var virtual_monitor in virtual_monitors) { | |
DisplayTransform transform; | |
int x, y; | |
if (layout.lookup (virtual_monitor.id, "(iiu)", out x, out y, out transform)) { | |
virtual_monitor.x = x; | |
virtual_monitor.y = y; | |
virtual_monitor.transform = transform; | |
} | |
} | |
} else { | |
// If no layout found, we save the current layout to use later | |
save_layout (virtual_monitors); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this class isn't necessary at all. There's just the settings object, and with the code cleaned, what rest is arrange_windows, save_layout, and get_layout_key.
all would be better residing in the MonitorManager class.
} | ||
} | ||
|
||
layout_manager.arrange_monitors (virtual_monitors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
disable_clone_mode is already re-arranging the monitors, so we only need to save it here.
layout_manager.arrange_monitors (virtual_monitors); | |
if (!is_mirrored) { | |
layout_manager.save_layouts (virtual_monitors); | |
} |
Fixes #418
Closes #412
Resolves #424
This PR introduces the
MonitorLayoutManager
class to persist and restore monitor arrangements when changes occur.We rely on Mutter's
DisplayConfig
D-Bus interface to detect monitor changes and retrieve the current monitor layout configuration (i.e., virtual monitors or CRTCs). However, there are some edge cases that cause layout issues:Clone Mode (Mirror Mode): When clone mode is enabled, Settings creates a single virtual monitor encompassing all physical monitors with identical coordinates (
x=0
,y=0
). When clone mode is disabled, Settings has no memory of the previous layout and falls back to overlapping display widgets, since all monitors share the same coordinates.Disabling Monitors: When a monitor is disabled, Mutter still reports its existence but omits its coordinates, as it’s no longer part of the active layout. This results in the disabled monitor’s widget overlapping another widget in the UI.
To address these issues, this PR introduces a
GSetting
to persist monitor layout data, including each monitor's position and transformation. This allows us to restore previous layouts accurately, even after mode toggles or monitor disable events.Additionally, this PR modifies when the display overlay updates its widgets — now triggered only when monitor changes are detected, ensuring a more stable and consistent display configuration.