diff --git a/data/display.gschema.xml b/data/display.gschema.xml new file mode 100644 index 00000000..405b702b --- /dev/null +++ b/data/display.gschema.xml @@ -0,0 +1,15 @@ + + + + + {} + Preferred display layouts + + Stores user-defined display layout profiles. + Each profile contains a unique identifier and a list of monitors, with their respective position (x, y) and other properties such as transformation (e.g., rotation). + This allows the system to restore or suggest preferred monitor arrangements and settings when displays are connected or configurations change. + + + + + \ No newline at end of file diff --git a/data/meson.build b/data/meson.build index bb32ce31..592d77a7 100644 --- a/data/meson.build +++ b/data/meson.build @@ -11,3 +11,9 @@ gresource = gnome.compile_resources( 'gresource', 'display.gresource.xml' ) + +install_data( + 'display.gschema.xml', + install_dir: datadir / 'glib-2.0' / 'schemas', + rename: 'io.elementary.settings.display.gschema.xml' +) \ No newline at end of file diff --git a/meson.build b/meson.build index 108fe1e8..cc921e71 100644 --- a/meson.build +++ b/meson.build @@ -31,3 +31,5 @@ config_file = configure_file( subdir('data') subdir('src') subdir('po') + +gnome.post_install(glib_compile_schemas: true) \ No newline at end of file diff --git a/src/Objects/MonitorLayoutManager.vala b/src/Objects/MonitorLayoutManager.vala new file mode 100644 index 00000000..c3ad2c85 --- /dev/null +++ b/src/Objects/MonitorLayoutManager.vala @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2025 elementary, Inc. + * + * Authored by: Leonardo Lemos + */ + +public class Display.MonitorLayoutManager : GLib.Object { + private Settings settings; + + private const string PREFERRED_MONITOR_LAYOUTS_KEY = "preferred-display-layouts"; + + public MonitorLayoutManager () { + Object (); + } + + construct { + settings = new Settings ("io.elementary.settings.display"); + } + + public void arrange_monitors (Gee.LinkedList virtual_monitors) { + if (virtual_monitors.size == 1) { + // If there's only one monitor, no need to arrange + // Cloned monitors only have one virtual monitor so will return here + return; + } + + var layout_key = get_layout_key (virtual_monitors); + // Layouts format are 'a{sa{sa{sv}}}' + var layouts = settings.get_value (PREFERRED_MONITOR_LAYOUTS_KEY); + VariantDict? monitors = null; + if (layouts == null && + layouts.lookup (layout_key, "a{sa{sv}}", out monitors) && + monitors != null) { + + foreach (var virtual_monitor in virtual_monitors) { + DisplayTransform transform; + int x, y; + if (monitors.lookup (virtual_monitor.id, "(iiu)", out x, out y, out transform)) { + virtual_monitor.x = x; + virtual_monitor.y = y; + virtual_monitor.transform = transform; + } + } + + return; + } + + // If no layout found, we save the current layout to use later + save_layout (virtual_monitors); + } + + public void save_layout (Gee.LinkedList virtual_monitors) { + //Build the layout variant + var dict_builder = new VariantDict (); + foreach (var monitor in virtual_monitors) { + var props_builder = new VariantDict (); + // We save three properties for now, may want to save more later + 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); + } + + var layout_variant = dict_builder.end (); + + // Add or update the layouts setting + var save_key = get_layout_key (virtual_monitors); + var layouts = settings.get_value (PREFERRED_MONITOR_LAYOUTS_KEY); + dict_builder = new VariantDict (layouts); + dict_builder.insert_value (save_key, layout_variant); + + // Save to settings + settings.set_value (PREFERRED_MONITOR_LAYOUTS_KEY, dict_builder.end ()); + } + + private string get_layout_key (Gee.LinkedList virtual_monitors) { + // Generate a unique key based on the virtual monitors' monitors hashes + var key = new StringBuilder (); + + foreach (var virtual_monitor in virtual_monitors) { + foreach (var monitor in virtual_monitor.monitors) { + key.append (virtual_monitor.id); + } + } + + return key.str; + } + + private bool is_virtual_monitors_cloned (Gee.LinkedList virtual_monitors) { + foreach (var monitor in virtual_monitors) { + if (monitor.x != 0 || monitor.y != 0) { + return false; + } + } + + return true; + } +} diff --git a/src/Objects/MonitorManager.vala b/src/Objects/MonitorManager.vala index 7e04b0e4..0c8f7ad1 100644 --- a/src/Objects/MonitorManager.vala +++ b/src/Objects/MonitorManager.vala @@ -46,8 +46,11 @@ public class Display.MonitorManager : GLib.Object { } } + public signal void monitors_changed (); + private MutterDisplayConfigInterface iface; private uint current_serial; + private MonitorLayoutManager layout_manager; private static MonitorManager monitor_manager; public static unowned MonitorManager get_default () { @@ -65,9 +68,16 @@ public class Display.MonitorManager : GLib.Object { construct { monitors = new Gee.LinkedList (); virtual_monitors = new Gee.LinkedList (); + layout_manager = new MonitorLayoutManager (); try { - iface = Bus.get_proxy_sync (BusType.SESSION, "org.gnome.Mutter.DisplayConfig", "/org/gnome/Mutter/DisplayConfig"); - iface.monitors_changed.connect (get_monitor_config); + iface = Bus.get_proxy_sync ( + BusType.SESSION, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig"); + iface.monitors_changed.connect (() => { + get_monitor_config (); + monitors_changed (); + }); } catch (Error e) { critical (e.message); } @@ -78,7 +88,10 @@ public class Display.MonitorManager : GLib.Object { MutterReadLogicalMonitor[] mutter_logical_monitors; GLib.HashTable properties; try { - iface.get_current_state (out current_serial, out mutter_monitors, out mutter_logical_monitors, out properties); + iface.get_current_state (out current_serial, + out mutter_monitors, + out mutter_logical_monitors, + out properties); } catch (Error e) { critical (e.message); } @@ -217,7 +230,7 @@ public class Display.MonitorManager : GLib.Object { virtual_monitor.scale = mutter_logical_monitor.scale; virtual_monitor.transform = mutter_logical_monitor.transform; virtual_monitor.primary = mutter_logical_monitor.primary; - add_virtual_monitor (virtual_monitor); + virtual_monitors.add (virtual_monitor); } // Look for any monitors that aren't part of a virtual monitor (hence disabled) @@ -237,9 +250,13 @@ public class Display.MonitorManager : GLib.Object { virtual_monitor.primary = false; virtual_monitor.monitors.add (monitor); virtual_monitor.scale = virtual_monitors[0].scale; - add_virtual_monitor (virtual_monitor); + virtual_monitors.add (virtual_monitor); } } + + if (!is_mirrored) { + layout_manager.save_layout (virtual_monitors); + } } public void set_monitor_config () throws Error { @@ -402,13 +419,10 @@ public class Display.MonitorManager : GLib.Object { virtual_monitors.clear (); virtual_monitors.add_all (new_virtual_monitors); - notify_property ("virtual-monitor-number"); - notify_property ("is-mirrored"); - } + layout_manager.arrange_monitors (virtual_monitors); - private void add_virtual_monitor (Display.VirtualMonitor virtual_monitor) { - virtual_monitors.add (virtual_monitor); notify_property ("virtual-monitor-number"); + notify_property ("is-mirrored"); } private VirtualMonitor? get_virtual_monitor_by_id (string id) { diff --git a/src/Widgets/DisplaysOverlay.vala b/src/Widgets/DisplaysOverlay.vala index 3874994d..6cfa3776 100644 --- a/src/Widgets/DisplaysOverlay.vala +++ b/src/Widgets/DisplaysOverlay.vala @@ -198,6 +198,7 @@ public class Display.DisplaysOverlay : Gtk.Box { add_output (virtual_monitor); } + show_windows (); change_active_displays_sensitivity (); calculate_ratio (); scanning = false; @@ -350,10 +351,6 @@ public class Display.DisplaysOverlay : Gtk.Box { check_configuration_change (); calculate_ratio (); }); - - if (!monitor_manager.is_mirrored && virtual_monitor.is_active) { - show_windows (); - } } private void set_as_primary (Display.VirtualMonitor new_primary) { diff --git a/src/meson.build b/src/meson.build index fc6df44b..6ed4f693 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,6 +10,7 @@ plug_files = files( 'Objects/MonitorMode.vala', 'Objects/MonitorManager.vala', 'Objects/Monitor.vala', + 'Objects/MonitorLayoutManager.vala', 'Views/NightLightView.vala', 'Views/DisplaysView.vala', 'Views' / 'FiltersView.vala',