4941 lines
152 KiB
Diff
4941 lines
152 KiB
Diff
From eb67a630584af8231a717c4030364a29e9e1fcaf Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 10:55:34 +0100
|
|
Subject: [PATCH 01/28] Introduce GNOME Display Control (gdctl) utility
|
|
|
|
It's based on `get-state.py`, but with the intention to expand its
|
|
functionality into not only listing information, but setting and
|
|
changing monitor configurations. It's meant to complement monitor
|
|
configurations from Settings with something that has more level of
|
|
control.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit a3cfd7bd91c6d98d0af30668fd1c39a73c790ac9)
|
|
---
|
|
tools/gdctl | 355 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
1 file changed, 355 insertions(+)
|
|
create mode 100755 tools/gdctl
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
new file mode 100755
|
|
index 0000000000..37b3a6b708
|
|
--- /dev/null
|
|
+++ b/tools/gdctl
|
|
@@ -0,0 +1,355 @@
|
|
+#!/usr/bin/env python3
|
|
+
|
|
+import argparse
|
|
+import sys
|
|
+
|
|
+from gi.repository import GLib, Gio
|
|
+
|
|
+NAME = "org.gnome.Mutter.DisplayConfig"
|
|
+INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
+OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig"
|
|
+
|
|
+
|
|
+TRANSFORM_STRINGS = {
|
|
+ 0: "normal",
|
|
+ 1: "90",
|
|
+ 2: "180",
|
|
+ 3: "270",
|
|
+ 4: "flipped",
|
|
+ 5: "flipped-90",
|
|
+ 6: "flipped-180",
|
|
+ 7: "flipped-270",
|
|
+}
|
|
+
|
|
+LAYOUT_MODE = {
|
|
+ 1: "logical",
|
|
+ 2: "physical",
|
|
+}
|
|
+
|
|
+
|
|
+def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
|
|
+ if is_last:
|
|
+ link = "└"
|
|
+ else:
|
|
+ link = "├"
|
|
+ padding = " "
|
|
+
|
|
+ if level >= 0:
|
|
+ indent = level
|
|
+ buffer = f"{link:{padding}>{indent * 4}}──{data}"
|
|
+ buffer = list(buffer)
|
|
+ for line in lines:
|
|
+ if line == level:
|
|
+ continue
|
|
+ index = line * 4
|
|
+ if line > 0:
|
|
+ index -= 1
|
|
+ buffer[index] = "│"
|
|
+ buffer = "".join(buffer)
|
|
+ else:
|
|
+ buffer = data
|
|
+
|
|
+ print(buffer)
|
|
+
|
|
+ if is_last and level in lines:
|
|
+ lines.remove(level)
|
|
+ elif not is_last and level not in lines:
|
|
+ lines.append(level)
|
|
+
|
|
+
|
|
+def maybe_describe(property_name, value):
|
|
+ enum_properties = {
|
|
+ "layout-mode": LAYOUT_MODE,
|
|
+ }
|
|
+
|
|
+ if property_name in enum_properties:
|
|
+ if isinstance(value, list):
|
|
+ return [
|
|
+ enum_properties[property_name].get(entry) for entry in value
|
|
+ ]
|
|
+ else:
|
|
+ return enum_properties[property_name].get(value)
|
|
+ else:
|
|
+ return value
|
|
+
|
|
+
|
|
+def print_properties(*, level, lines, properties):
|
|
+ property_keys = list(properties.keys())
|
|
+
|
|
+ print_data(
|
|
+ level=level,
|
|
+ is_last=True,
|
|
+ lines=lines,
|
|
+ data=f"Properties: ({len(property_keys)})",
|
|
+ )
|
|
+ for key in property_keys:
|
|
+ is_last = key == property_keys[-1]
|
|
+ value_string = maybe_describe(key, properties[key])
|
|
+ print_data(
|
|
+ level=level + 1,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data=f"{key} ⇒ {value_string}",
|
|
+ )
|
|
+
|
|
+
|
|
+def strip_dbus_error_prefix(message):
|
|
+ if message.startswith("GDBus.Error"):
|
|
+ return message.partition(" ")[2]
|
|
+ else:
|
|
+ return message
|
|
+
|
|
+
|
|
+class MonitorsState:
|
|
+ STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
+ "(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
+ )
|
|
+
|
|
+ def __init__(self):
|
|
+ self.current_state = self.get_current_state()
|
|
+
|
|
+ def get_current_state(self) -> GLib.Variant:
|
|
+ raise NotImplementedError()
|
|
+
|
|
+ def print_mode(self, mode, is_last, show_properties, lines):
|
|
+ print_data(level=2, is_last=is_last, lines=lines, data=f"{mode[0]}")
|
|
+
|
|
+ if not show_properties:
|
|
+ return
|
|
+
|
|
+ print_data(
|
|
+ level=3,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Dimension: {mode[1]}x{mode[2]}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=3,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Refresh rate: {mode[3]:.3f}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=3,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Preferred scale: {mode[4]}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=3,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Supported scales: {mode[5]}",
|
|
+ )
|
|
+
|
|
+ if show_properties:
|
|
+ mode_properties = mode[6]
|
|
+ print_properties(level=3, lines=lines, properties=mode_properties)
|
|
+
|
|
+ def print_current_state(self, show_modes=False, show_properties=False):
|
|
+ variant = self.current_state
|
|
+
|
|
+ print("Monitors:")
|
|
+ monitors = variant[1]
|
|
+ lines = []
|
|
+ for monitor in monitors:
|
|
+ is_last = monitor == monitors[-1]
|
|
+ spec = monitor[0]
|
|
+ modes = monitor[1]
|
|
+ properties = monitor[2]
|
|
+ print_data(
|
|
+ level=0,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data="Monitor {}".format(spec[0]),
|
|
+ )
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"EDID: vendor: {spec[1]}, product: {spec[2]}, serial: {spec[3]}",
|
|
+ )
|
|
+
|
|
+ if show_modes:
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=not show_properties,
|
|
+ lines=lines,
|
|
+ data=f"Modes ({len(modes)})",
|
|
+ )
|
|
+ for mode in modes:
|
|
+ is_last = mode == modes[-1]
|
|
+ self.print_mode(mode, is_last, show_properties, lines)
|
|
+ else:
|
|
+ mode = next(
|
|
+ (mode for mode in modes if "is-current" in mode[6]), None
|
|
+ )
|
|
+ if mode:
|
|
+ mode_type = "Current"
|
|
+ else:
|
|
+ mode = next(
|
|
+ (mode for mode in modes if "is-preferred" in mode[6]),
|
|
+ None,
|
|
+ )
|
|
+ if mode:
|
|
+ mode_type = "Preferred"
|
|
+
|
|
+ if mode:
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=not show_properties,
|
|
+ lines=lines,
|
|
+ data=f"{mode_type} mode",
|
|
+ )
|
|
+ self.print_mode(mode, True, show_properties, lines)
|
|
+
|
|
+ if show_properties:
|
|
+ print_properties(level=1, lines=lines, properties=properties)
|
|
+
|
|
+ print()
|
|
+ print("Logical monitors:")
|
|
+ logical_monitors = variant[2]
|
|
+ index = 1
|
|
+ for logical_monitor in logical_monitors:
|
|
+ is_last = logical_monitor == logical_monitors[-1]
|
|
+ properties = logical_monitor[2]
|
|
+ print_data(
|
|
+ level=0,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data=f"Logical monitor #{index}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Position: ({logical_monitor[0]}, {logical_monitor[1]})",
|
|
+ )
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Scale: {logical_monitor[2]}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Transform: {TRANSFORM_STRINGS.get(logical_monitor[3])}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Primary: {logical_monitor[4]}",
|
|
+ )
|
|
+ monitors = logical_monitor[5]
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=not show_properties,
|
|
+ lines=lines,
|
|
+ data=f"Monitors: ({len(monitors)})",
|
|
+ )
|
|
+ for monitor in monitors:
|
|
+ is_last = monitor == monitors[-1]
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data=f"{monitor[0]} ({monitor[1]}, {monitor[2]}, {monitor[3]})",
|
|
+ )
|
|
+
|
|
+ if show_properties:
|
|
+ properties = logical_monitor[6]
|
|
+ print_properties(level=1, lines=lines, properties=properties)
|
|
+
|
|
+ index += 1
|
|
+
|
|
+ if show_properties:
|
|
+ properties = variant[3]
|
|
+ print()
|
|
+ print_properties(level=-1, lines=lines, properties=properties)
|
|
+
|
|
+
|
|
+class MonitorsStateDBus(MonitorsState):
|
|
+ def __init__(self):
|
|
+ self._proxy = Gio.DBusProxy.new_for_bus_sync(
|
|
+ bus_type=Gio.BusType.SESSION,
|
|
+ flags=Gio.DBusProxyFlags.NONE,
|
|
+ info=None,
|
|
+ name=NAME,
|
|
+ object_path=OBJECT_PATH,
|
|
+ interface_name=INTERFACE,
|
|
+ cancellable=None,
|
|
+ )
|
|
+ super().__init__()
|
|
+
|
|
+ def get_current_state(self) -> GLib.Variant:
|
|
+ variant = self._proxy.call_sync(
|
|
+ method_name="GetCurrentState",
|
|
+ parameters=None,
|
|
+ flags=Gio.DBusCallFlags.NO_AUTO_START,
|
|
+ timeout_msec=-1,
|
|
+ cancellable=None,
|
|
+ )
|
|
+ assert variant.get_type().equal(self.STATE_VARIANT_TYPE)
|
|
+ return variant
|
|
+
|
|
+
|
|
+if __name__ == "__main__":
|
|
+ parser = argparse.ArgumentParser(description="Display control utility")
|
|
+
|
|
+ subparser = parser.add_subparsers(
|
|
+ dest="command",
|
|
+ title="The following commands are available",
|
|
+ metavar="COMMAND",
|
|
+ required=True,
|
|
+ )
|
|
+ show_parser = subparser.add_parser(
|
|
+ "show", help="Show display configuration"
|
|
+ )
|
|
+ show_parser.add_argument(
|
|
+ "-m",
|
|
+ "--modes",
|
|
+ action="store_true",
|
|
+ help="List available monitor modes",
|
|
+ )
|
|
+ show_parser.add_argument(
|
|
+ "-p",
|
|
+ "--properties",
|
|
+ action="store_true",
|
|
+ help="List properties",
|
|
+ )
|
|
+ show_parser.add_argument(
|
|
+ "-v",
|
|
+ "--verbose",
|
|
+ action="store_true",
|
|
+ help="Display all available information",
|
|
+ )
|
|
+
|
|
+ args = parser.parse_args()
|
|
+
|
|
+ match args.command:
|
|
+ case "show":
|
|
+ try:
|
|
+ monitors_state = MonitorsStateDBus()
|
|
+ except GLib.Error as e:
|
|
+ if e.domain == GLib.quark_to_string(Gio.DBusError.quark()):
|
|
+ error_message = strip_dbus_error_prefix(e.message)
|
|
+ print(
|
|
+ f"Failed retrieve current state: {error_message}",
|
|
+ file=sys.stderr,
|
|
+ )
|
|
+ sys.exit(1)
|
|
+
|
|
+ if args.verbose:
|
|
+ show_modes = True
|
|
+ show_properties = True
|
|
+ else:
|
|
+ show_modes = args.modes
|
|
+ show_properties = args.properties
|
|
+
|
|
+ monitors_state.print_current_state(
|
|
+ show_modes=show_modes,
|
|
+ show_properties=show_properties,
|
|
+ )
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 9fe33ced8e25428c4f1bf626cdc9245cb87d875a Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 11:35:44 +0100
|
|
Subject: [PATCH 02/28] gdctl: Add helpers to get relevant state variants
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 3469530deca21f8354250c0e865449f370135e8c)
|
|
---
|
|
tools/gdctl | 17 ++++++++++++-----
|
|
1 file changed, 12 insertions(+), 5 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 37b3a6b708..fb3d429c8f 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -111,6 +111,15 @@ class MonitorsState:
|
|
def get_current_state(self) -> GLib.Variant:
|
|
raise NotImplementedError()
|
|
|
|
+ def get_monitors_variant(self):
|
|
+ return self.current_state[1]
|
|
+
|
|
+ def get_logical_monitors_variant(self):
|
|
+ return self.current_state[2]
|
|
+
|
|
+ def get_properties_variant(self):
|
|
+ return self.current_state[3]
|
|
+
|
|
def print_mode(self, mode, is_last, show_properties, lines):
|
|
print_data(level=2, is_last=is_last, lines=lines, data=f"{mode[0]}")
|
|
|
|
@@ -147,10 +156,8 @@ class MonitorsState:
|
|
print_properties(level=3, lines=lines, properties=mode_properties)
|
|
|
|
def print_current_state(self, show_modes=False, show_properties=False):
|
|
- variant = self.current_state
|
|
-
|
|
print("Monitors:")
|
|
- monitors = variant[1]
|
|
+ monitors = self.get_monitors_variant()
|
|
lines = []
|
|
for monitor in monitors:
|
|
is_last = monitor == monitors[-1]
|
|
@@ -208,7 +215,7 @@ class MonitorsState:
|
|
|
|
print()
|
|
print("Logical monitors:")
|
|
- logical_monitors = variant[2]
|
|
+ logical_monitors = self.get_logical_monitors_variant()
|
|
index = 1
|
|
for logical_monitor in logical_monitors:
|
|
is_last = logical_monitor == logical_monitors[-1]
|
|
@@ -266,7 +273,7 @@ class MonitorsState:
|
|
index += 1
|
|
|
|
if show_properties:
|
|
- properties = variant[3]
|
|
+ properties = self.get_properties_variant()
|
|
print()
|
|
print_properties(level=-1, lines=lines, properties=properties)
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From f5b876f8b13423bbec62e7fd3d29daba21d0b40a Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 11:56:53 +0100
|
|
Subject: [PATCH 03/28] gdtl: Add Monitor class
|
|
|
|
This makes it possible to avoid dealing directly with the variant when
|
|
operating on a monitor.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit cc11b0682bad096351ca56472e8ec7edbf4a9e92)
|
|
---
|
|
tools/gdctl | 108 +++++++++++++++++++++++++++++++++++++++++++---------
|
|
1 file changed, 89 insertions(+), 19 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index fb3d429c8f..7373243159 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -3,6 +3,7 @@
|
|
import argparse
|
|
import sys
|
|
|
|
+from dataclasses import dataclass
|
|
from gi.repository import GLib, Gio
|
|
|
|
NAME = "org.gnome.Mutter.DisplayConfig"
|
|
@@ -100,6 +101,45 @@ def strip_dbus_error_prefix(message):
|
|
return message
|
|
|
|
|
|
+@dataclass
|
|
+class MonitorMode:
|
|
+ name: str
|
|
+ resolution: tuple[int, int]
|
|
+ refresh_rate: float
|
|
+ preferred_scale: float
|
|
+ supported_scales: list[float]
|
|
+ refresh_rate: float
|
|
+ properties: dict
|
|
+
|
|
+ @classmethod
|
|
+ def from_variant(cls, variant):
|
|
+ return cls(
|
|
+ name=variant[0],
|
|
+ resolution=(variant[1], variant[2]),
|
|
+ refresh_rate=variant[3],
|
|
+ preferred_scale=variant[4],
|
|
+ supported_scales=variant[5],
|
|
+ properties=variant[6],
|
|
+ )
|
|
+
|
|
+
|
|
+class Monitor:
|
|
+ def __init__(self, variant):
|
|
+ self.init_from_variant(variant)
|
|
+
|
|
+ def init_from_variant(self, variant):
|
|
+ spec = variant[0]
|
|
+ self.connector = spec[0]
|
|
+ self.vendor = spec[1] if spec[1] != "" else None
|
|
+ self.product = spec[2] if spec[2] != "" else None
|
|
+ self.serial = spec[3] if spec[3] != "" else None
|
|
+ self.modes = [
|
|
+ MonitorMode.from_variant(mode_variant)
|
|
+ for mode_variant in variant[1]
|
|
+ ]
|
|
+ self.properties = variant[2]
|
|
+
|
|
+
|
|
class MonitorsState:
|
|
STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
"(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
@@ -108,9 +148,17 @@ class MonitorsState:
|
|
def __init__(self):
|
|
self.current_state = self.get_current_state()
|
|
|
|
+ self.init_monitors()
|
|
+
|
|
def get_current_state(self) -> GLib.Variant:
|
|
raise NotImplementedError()
|
|
|
|
+ def init_monitors(self):
|
|
+ self.monitors = {}
|
|
+ for monitor_variant in self.get_monitors_variant():
|
|
+ monitor = Monitor(monitor_variant)
|
|
+ self.monitors[monitor.connector] = monitor
|
|
+
|
|
def get_monitors_variant(self):
|
|
return self.current_state[1]
|
|
|
|
@@ -121,62 +169,79 @@ class MonitorsState:
|
|
return self.current_state[3]
|
|
|
|
def print_mode(self, mode, is_last, show_properties, lines):
|
|
- print_data(level=2, is_last=is_last, lines=lines, data=f"{mode[0]}")
|
|
+ print_data(level=2, is_last=is_last, lines=lines, data=f"{mode.name}")
|
|
|
|
if not show_properties:
|
|
return
|
|
|
|
+ width, height = mode.resolution
|
|
print_data(
|
|
level=3,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Dimension: {mode[1]}x{mode[2]}",
|
|
+ data=f"Dimension: {width}x{height}",
|
|
)
|
|
print_data(
|
|
level=3,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Refresh rate: {mode[3]:.3f}",
|
|
+ data=f"Refresh rate: {mode.refresh_rate:.3f}",
|
|
)
|
|
print_data(
|
|
level=3,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Preferred scale: {mode[4]}",
|
|
+ data=f"Preferred scale: {mode.preferred_scale}",
|
|
)
|
|
print_data(
|
|
level=3,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Supported scales: {mode[5]}",
|
|
+ data=f"Supported scales: {mode.supported_scales}",
|
|
)
|
|
|
|
if show_properties:
|
|
- mode_properties = mode[6]
|
|
+ mode_properties = mode.properties
|
|
print_properties(level=3, lines=lines, properties=mode_properties)
|
|
|
|
def print_current_state(self, show_modes=False, show_properties=False):
|
|
print("Monitors:")
|
|
- monitors = self.get_monitors_variant()
|
|
lines = []
|
|
+ monitors = list(self.monitors.values())
|
|
for monitor in monitors:
|
|
is_last = monitor == monitors[-1]
|
|
- spec = monitor[0]
|
|
- modes = monitor[1]
|
|
- properties = monitor[2]
|
|
+ modes = monitor.modes
|
|
+ properties = monitor.properties
|
|
+
|
|
print_data(
|
|
level=0,
|
|
is_last=is_last,
|
|
lines=lines,
|
|
- data="Monitor {}".format(spec[0]),
|
|
- )
|
|
- print_data(
|
|
- level=1,
|
|
- is_last=False,
|
|
- lines=lines,
|
|
- data=f"EDID: vendor: {spec[1]}, product: {spec[2]}, serial: {spec[3]}",
|
|
+ data=f"Monitor {monitor.connector}",
|
|
)
|
|
|
|
+ if monitor.vendor:
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Vendor: {monitor.vendor}",
|
|
+ )
|
|
+ if monitor.product:
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Product: {monitor.product}",
|
|
+ )
|
|
+ if monitor.serial:
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Serial: {monitor.serial}",
|
|
+ )
|
|
+
|
|
if show_modes:
|
|
print_data(
|
|
level=1,
|
|
@@ -189,13 +254,18 @@ class MonitorsState:
|
|
self.print_mode(mode, is_last, show_properties, lines)
|
|
else:
|
|
mode = next(
|
|
- (mode for mode in modes if "is-current" in mode[6]), None
|
|
+ (mode for mode in modes if "is-current" in mode.properties),
|
|
+ None,
|
|
)
|
|
if mode:
|
|
mode_type = "Current"
|
|
else:
|
|
mode = next(
|
|
- (mode for mode in modes if "is-preferred" in mode[6]),
|
|
+ (
|
|
+ mode
|
|
+ for mode in modes
|
|
+ if "is-preferred" in mode.properties
|
|
+ ),
|
|
None,
|
|
)
|
|
if mode:
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From beb9cc9fc1a6330085692fbe416f4d27bbd3df66 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 12:10:18 +0100
|
|
Subject: [PATCH 04/28] gdctl: Always display monitor display name if available
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 00d5a6a0cd0c207a2b9831504ce5938ad2264052)
|
|
---
|
|
tools/gdctl | 11 ++++++++++-
|
|
1 file changed, 10 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 7373243159..d8f1349d36 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -139,6 +139,8 @@ class Monitor:
|
|
]
|
|
self.properties = variant[2]
|
|
|
|
+ self.display_name = self.properties.get("display-name", None)
|
|
+
|
|
|
|
class MonitorsState:
|
|
STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
@@ -213,11 +215,18 @@ class MonitorsState:
|
|
modes = monitor.modes
|
|
properties = monitor.properties
|
|
|
|
+ if monitor.display_name:
|
|
+ monitor_title = (
|
|
+ f"Monitor {monitor.connector} ({monitor.display_name})"
|
|
+ )
|
|
+ else:
|
|
+ monitor_title = f"Monitor {monitor.connector}"
|
|
+
|
|
print_data(
|
|
level=0,
|
|
is_last=is_last,
|
|
lines=lines,
|
|
- data=f"Monitor {monitor.connector}",
|
|
+ data=monitor_title,
|
|
)
|
|
|
|
if monitor.vendor:
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 778fbb85685b6a4ddeac3c43e6764db8178550ef Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 12:51:57 +0100
|
|
Subject: [PATCH 05/28] gdctl: Add LogicalMonitor class
|
|
|
|
This abstracts what makes a logical monitor. Will make it easy doing
|
|
manipulations, and makes the print function a bit more readable.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 34195d905a4f07edfb56d4278896c1ad37154f8b)
|
|
---
|
|
tools/gdctl | 115 ++++++++++++++++++++++++++++++++++++++++++----------
|
|
1 file changed, 94 insertions(+), 21 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index d8f1349d36..e67dc4b240 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -5,22 +5,42 @@ import sys
|
|
|
|
from dataclasses import dataclass
|
|
from gi.repository import GLib, Gio
|
|
+from enum import Enum
|
|
|
|
NAME = "org.gnome.Mutter.DisplayConfig"
|
|
INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig"
|
|
|
|
|
|
-TRANSFORM_STRINGS = {
|
|
- 0: "normal",
|
|
- 1: "90",
|
|
- 2: "180",
|
|
- 3: "270",
|
|
- 4: "flipped",
|
|
- 5: "flipped-90",
|
|
- 6: "flipped-180",
|
|
- 7: "flipped-270",
|
|
-}
|
|
+class Transform(Enum):
|
|
+ NORMAL = 0
|
|
+ ROTATE_90 = 1
|
|
+ ROTATE_180 = 2
|
|
+ ROTATE_270 = 3
|
|
+ FLIPPED = 4
|
|
+ ROTATE_90_FLIPPED = 5
|
|
+ ROTATE_270_FLIPPED = 6
|
|
+ ROTATE_180_FLIPPED = 7
|
|
+
|
|
+ def __str__(self):
|
|
+ match self:
|
|
+ case self.NORMAL:
|
|
+ return "normal"
|
|
+ case self.ROTATE_90:
|
|
+ return "90"
|
|
+ case self.ROTATE_180:
|
|
+ return "180"
|
|
+ case self.ROTATE_270:
|
|
+ return "270"
|
|
+ case self.FLIPPED:
|
|
+ return "flipped"
|
|
+ case self.ROTATE_90_FLIPPED:
|
|
+ return "flipped-90"
|
|
+ case self.ROTATE_180_FLIPPED:
|
|
+ return "flipped-180"
|
|
+ case self.ROTATE_270_FLIPPED:
|
|
+ return "flipped-270"
|
|
+
|
|
|
|
LAYOUT_MODE = {
|
|
1: "logical",
|
|
@@ -142,6 +162,45 @@ class Monitor:
|
|
self.display_name = self.properties.get("display-name", None)
|
|
|
|
|
|
+class LogicalMonitor:
|
|
+ def __init__(
|
|
+ self,
|
|
+ monitors,
|
|
+ scale,
|
|
+ position=(0, 0),
|
|
+ transform=Transform.NORMAL,
|
|
+ is_primary=False,
|
|
+ properties={},
|
|
+ ):
|
|
+ self.position = position
|
|
+ self.scale = scale
|
|
+ self.transform = transform
|
|
+ self.is_primary = is_primary
|
|
+ self.monitors = monitors
|
|
+ self.properties = properties
|
|
+
|
|
+ @classmethod
|
|
+ def new_from_variant(cls, monitors_state, variant):
|
|
+ position = (variant[0], variant[1])
|
|
+ scale = variant[2]
|
|
+ transform = Transform(variant[3])
|
|
+ is_primary = variant[4]
|
|
+ connectors = [connector for connector, _, _, _ in variant[5]]
|
|
+ monitors = [
|
|
+ monitors_state.monitors[connector] for connector in connectors
|
|
+ ]
|
|
+ properties = variant[6]
|
|
+
|
|
+ return cls(
|
|
+ monitors=monitors,
|
|
+ position=position,
|
|
+ scale=scale,
|
|
+ transform=transform,
|
|
+ is_primary=is_primary,
|
|
+ properties=properties,
|
|
+ )
|
|
+
|
|
+
|
|
class MonitorsState:
|
|
STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
"(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
@@ -151,6 +210,7 @@ class MonitorsState:
|
|
self.current_state = self.get_current_state()
|
|
|
|
self.init_monitors()
|
|
+ self.init_logical_monitors()
|
|
|
|
def get_current_state(self) -> GLib.Variant:
|
|
raise NotImplementedError()
|
|
@@ -161,6 +221,12 @@ class MonitorsState:
|
|
monitor = Monitor(monitor_variant)
|
|
self.monitors[monitor.connector] = monitor
|
|
|
|
+ def init_logical_monitors(self):
|
|
+ self.logical_monitors = []
|
|
+ for variant in self.get_logical_monitors_variant():
|
|
+ logical_monitor = LogicalMonitor.new_from_variant(self, variant)
|
|
+ self.logical_monitors.append(logical_monitor)
|
|
+
|
|
def get_monitors_variant(self):
|
|
return self.current_state[1]
|
|
|
|
@@ -294,42 +360,41 @@ class MonitorsState:
|
|
|
|
print()
|
|
print("Logical monitors:")
|
|
- logical_monitors = self.get_logical_monitors_variant()
|
|
index = 1
|
|
- for logical_monitor in logical_monitors:
|
|
- is_last = logical_monitor == logical_monitors[-1]
|
|
- properties = logical_monitor[2]
|
|
+ for logical_monitor in self.logical_monitors:
|
|
+ is_last = logical_monitor == self.logical_monitors[-1]
|
|
print_data(
|
|
level=0,
|
|
is_last=is_last,
|
|
lines=lines,
|
|
data=f"Logical monitor #{index}",
|
|
)
|
|
+ (x, y) = logical_monitor.position
|
|
print_data(
|
|
level=1,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Position: ({logical_monitor[0]}, {logical_monitor[1]})",
|
|
+ data=f"Position: ({x}, {y})",
|
|
)
|
|
print_data(
|
|
level=1,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Scale: {logical_monitor[2]}",
|
|
+ data=f"Scale: {logical_monitor.scale}",
|
|
)
|
|
print_data(
|
|
level=1,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Transform: {TRANSFORM_STRINGS.get(logical_monitor[3])}",
|
|
+ data=f"Transform: {logical_monitor.transform}",
|
|
)
|
|
print_data(
|
|
level=1,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Primary: {logical_monitor[4]}",
|
|
+ data=f"Primary: {'yes' if logical_monitor.is_primary else 'no'}",
|
|
)
|
|
- monitors = logical_monitor[5]
|
|
+ monitors = logical_monitor.monitors
|
|
print_data(
|
|
level=1,
|
|
is_last=not show_properties,
|
|
@@ -338,15 +403,23 @@ class MonitorsState:
|
|
)
|
|
for monitor in monitors:
|
|
is_last = monitor == monitors[-1]
|
|
+
|
|
+ if monitor.display_name:
|
|
+ monitor_title = (
|
|
+ f"{monitor.connector} ({monitor.display_name})"
|
|
+ )
|
|
+ else:
|
|
+ monitor_title = f"{monitor.connector}"
|
|
+
|
|
print_data(
|
|
level=2,
|
|
is_last=is_last,
|
|
lines=lines,
|
|
- data=f"{monitor[0]} ({monitor[1]}, {monitor[2]}, {monitor[3]})",
|
|
+ data=monitor_title,
|
|
)
|
|
|
|
if show_properties:
|
|
- properties = logical_monitor[6]
|
|
+ properties = logical_monitor.properties
|
|
print_properties(level=1, lines=lines, properties=properties)
|
|
|
|
index += 1
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From d435bea71f742537b79299ec935dfcd57d0d22b4 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 17:16:37 +0100
|
|
Subject: [PATCH 06/28] gdctl: Store property enum values as real enums
|
|
|
|
This means declaring an enum class inheriting from enum.Enum.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 481f046cd52deac332b3777123bac7efeff51955)
|
|
---
|
|
tools/gdctl | 69 ++++++++++++++++++++++++++++++++++-------------------
|
|
1 file changed, 44 insertions(+), 25 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index e67dc4b240..4b0ca244f0 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -42,10 +42,36 @@ class Transform(Enum):
|
|
return "flipped-270"
|
|
|
|
|
|
-LAYOUT_MODE = {
|
|
- 1: "logical",
|
|
- 2: "physical",
|
|
-}
|
|
+class LayoutMode(Enum):
|
|
+ LOGICAL = 1
|
|
+ PHYSICAL = 2
|
|
+
|
|
+ def __str__(self):
|
|
+ match self:
|
|
+ case self.LOGICAL:
|
|
+ return "logical"
|
|
+ case self.PHYSICAL:
|
|
+ return "physical"
|
|
+
|
|
+
|
|
+def translate_property(name, value):
|
|
+ enum_properties = {
|
|
+ "layout-mode": LayoutMode,
|
|
+ }
|
|
+
|
|
+ if name in enum_properties:
|
|
+ if isinstance(value, list):
|
|
+ return [enum_properties[name](element) for element in value]
|
|
+ else:
|
|
+ return enum_properties[name](value)
|
|
+ else:
|
|
+ return value
|
|
+
|
|
+
|
|
+def translate_properties(variant):
|
|
+ return {
|
|
+ key: translate_property(key, value) for key, value in variant.items()
|
|
+ }
|
|
|
|
|
|
def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
|
|
@@ -78,22 +104,6 @@ def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
|
|
lines.append(level)
|
|
|
|
|
|
-def maybe_describe(property_name, value):
|
|
- enum_properties = {
|
|
- "layout-mode": LAYOUT_MODE,
|
|
- }
|
|
-
|
|
- if property_name in enum_properties:
|
|
- if isinstance(value, list):
|
|
- return [
|
|
- enum_properties[property_name].get(entry) for entry in value
|
|
- ]
|
|
- else:
|
|
- return enum_properties[property_name].get(value)
|
|
- else:
|
|
- return value
|
|
-
|
|
-
|
|
def print_properties(*, level, lines, properties):
|
|
property_keys = list(properties.keys())
|
|
|
|
@@ -105,7 +115,14 @@ def print_properties(*, level, lines, properties):
|
|
)
|
|
for key in property_keys:
|
|
is_last = key == property_keys[-1]
|
|
- value_string = maybe_describe(key, properties[key])
|
|
+
|
|
+ value = properties[key]
|
|
+ if isinstance(value, list):
|
|
+ elements_string = ", ".join([str(element) for element in value])
|
|
+ value_string = f"[{elements_string}]"
|
|
+ else:
|
|
+ value_string = str(value)
|
|
+
|
|
print_data(
|
|
level=level + 1,
|
|
is_last=is_last,
|
|
@@ -139,7 +156,7 @@ class MonitorMode:
|
|
refresh_rate=variant[3],
|
|
preferred_scale=variant[4],
|
|
supported_scales=variant[5],
|
|
- properties=variant[6],
|
|
+ properties=translate_properties(variant[6]),
|
|
)
|
|
|
|
|
|
@@ -157,7 +174,7 @@ class Monitor:
|
|
MonitorMode.from_variant(mode_variant)
|
|
for mode_variant in variant[1]
|
|
]
|
|
- self.properties = variant[2]
|
|
+ self.properties = translate_properties(variant[2])
|
|
|
|
self.display_name = self.properties.get("display-name", None)
|
|
|
|
@@ -189,7 +206,7 @@ class LogicalMonitor:
|
|
monitors = [
|
|
monitors_state.monitors[connector] for connector in connectors
|
|
]
|
|
- properties = variant[6]
|
|
+ properties = translate_properties(variant[6])
|
|
|
|
return cls(
|
|
monitors=monitors,
|
|
@@ -209,6 +226,8 @@ class MonitorsState:
|
|
def __init__(self):
|
|
self.current_state = self.get_current_state()
|
|
|
|
+ self.properties = translate_properties(self.current_state[3])
|
|
+
|
|
self.init_monitors()
|
|
self.init_logical_monitors()
|
|
|
|
@@ -425,7 +444,7 @@ class MonitorsState:
|
|
index += 1
|
|
|
|
if show_properties:
|
|
- properties = self.get_properties_variant()
|
|
+ properties = self.properties
|
|
print()
|
|
print_properties(level=-1, lines=lines, properties=properties)
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 924c440c17e71281e748f6384db2441abd259247 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Thu, 19 Dec 2024 21:41:14 +0100
|
|
Subject: [PATCH 07/28] gdctl: Display booleans as yes / no
|
|
|
|
Slightly more human readible and less programmer speak.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit e326aed70ef811f77b2ec21a8f4ec315f2028403)
|
|
---
|
|
tools/gdctl | 2 ++
|
|
1 file changed, 2 insertions(+)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 4b0ca244f0..c8211edea8 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -120,6 +120,8 @@ def print_properties(*, level, lines, properties):
|
|
if isinstance(value, list):
|
|
elements_string = ", ".join([str(element) for element in value])
|
|
value_string = f"[{elements_string}]"
|
|
+ elif isinstance(value, bool):
|
|
+ value_string = "yes" if value else "no"
|
|
else:
|
|
value_string = str(value)
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 279d99fc893d11523698ae2fefc4f4876cc38c2b Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 21:09:51 +0100
|
|
Subject: [PATCH 08/28] gdctl: Hook up to o.g.M.DisplayConfig via dedicated
|
|
class
|
|
|
|
The "current state" one will use DisplayConfig to query, which
|
|
eventually will also do configuration method calls.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 7ddaf23130141a34a29be143c350824109f88ace)
|
|
---
|
|
tools/gdctl | 67 +++++++++++++++++++++++++----------------------------
|
|
1 file changed, 32 insertions(+), 35 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index c8211edea8..00b437648f 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -140,6 +140,34 @@ def strip_dbus_error_prefix(message):
|
|
return message
|
|
|
|
|
|
+class DisplayConfig:
|
|
+ STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
+ "(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
+ )
|
|
+
|
|
+ def __init__(self):
|
|
+ self._proxy = Gio.DBusProxy.new_for_bus_sync(
|
|
+ bus_type=Gio.BusType.SESSION,
|
|
+ flags=Gio.DBusProxyFlags.NONE,
|
|
+ info=None,
|
|
+ name=NAME,
|
|
+ object_path=OBJECT_PATH,
|
|
+ interface_name=INTERFACE,
|
|
+ cancellable=None,
|
|
+ )
|
|
+
|
|
+ def get_current_state(self) -> GLib.Variant:
|
|
+ variant = self._proxy.call_sync(
|
|
+ method_name="GetCurrentState",
|
|
+ parameters=None,
|
|
+ flags=Gio.DBusCallFlags.NO_AUTO_START,
|
|
+ timeout_msec=-1,
|
|
+ cancellable=None,
|
|
+ )
|
|
+ assert variant.get_type().equal(self.STATE_VARIANT_TYPE)
|
|
+ return variant
|
|
+
|
|
+
|
|
@dataclass
|
|
class MonitorMode:
|
|
name: str
|
|
@@ -221,21 +249,14 @@ class LogicalMonitor:
|
|
|
|
|
|
class MonitorsState:
|
|
- STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
- "(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
- )
|
|
-
|
|
- def __init__(self):
|
|
- self.current_state = self.get_current_state()
|
|
+ def __init__(self, display_config):
|
|
+ self.current_state = display_config.get_current_state()
|
|
|
|
self.properties = translate_properties(self.current_state[3])
|
|
|
|
self.init_monitors()
|
|
self.init_logical_monitors()
|
|
|
|
- def get_current_state(self) -> GLib.Variant:
|
|
- raise NotImplementedError()
|
|
-
|
|
def init_monitors(self):
|
|
self.monitors = {}
|
|
for monitor_variant in self.get_monitors_variant():
|
|
@@ -451,31 +472,6 @@ class MonitorsState:
|
|
print_properties(level=-1, lines=lines, properties=properties)
|
|
|
|
|
|
-class MonitorsStateDBus(MonitorsState):
|
|
- def __init__(self):
|
|
- self._proxy = Gio.DBusProxy.new_for_bus_sync(
|
|
- bus_type=Gio.BusType.SESSION,
|
|
- flags=Gio.DBusProxyFlags.NONE,
|
|
- info=None,
|
|
- name=NAME,
|
|
- object_path=OBJECT_PATH,
|
|
- interface_name=INTERFACE,
|
|
- cancellable=None,
|
|
- )
|
|
- super().__init__()
|
|
-
|
|
- def get_current_state(self) -> GLib.Variant:
|
|
- variant = self._proxy.call_sync(
|
|
- method_name="GetCurrentState",
|
|
- parameters=None,
|
|
- flags=Gio.DBusCallFlags.NO_AUTO_START,
|
|
- timeout_msec=-1,
|
|
- cancellable=None,
|
|
- )
|
|
- assert variant.get_type().equal(self.STATE_VARIANT_TYPE)
|
|
- return variant
|
|
-
|
|
-
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Display control utility")
|
|
|
|
@@ -512,7 +508,8 @@ if __name__ == "__main__":
|
|
match args.command:
|
|
case "show":
|
|
try:
|
|
- monitors_state = MonitorsStateDBus()
|
|
+ display_config = DisplayConfig()
|
|
+ monitors_state = MonitorsState(display_config)
|
|
except GLib.Error as e:
|
|
if e.domain == GLib.quark_to_string(Gio.DBusError.quark()):
|
|
error_message = strip_dbus_error_prefix(e.message)
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 1e90c7fb084a670d3ffb4a972e83a402324e4d35 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 13 Dec 2024 21:18:43 +0100
|
|
Subject: [PATCH 09/28] gdctl/monitors-state: Don't keep current state variant
|
|
around
|
|
|
|
It's not used by anything, all data is accessed by data structures
|
|
derived from the variant.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 2ee918a9491c1bc23db6df7531e93f8b4a224613)
|
|
---
|
|
tools/gdctl | 25 ++++++++-----------------
|
|
1 file changed, 8 insertions(+), 17 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 00b437648f..3b47a1c842 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -250,34 +250,25 @@ class LogicalMonitor:
|
|
|
|
class MonitorsState:
|
|
def __init__(self, display_config):
|
|
- self.current_state = display_config.get_current_state()
|
|
+ current_state = display_config.get_current_state()
|
|
|
|
- self.properties = translate_properties(self.current_state[3])
|
|
+ self.properties = translate_properties(current_state[3])
|
|
|
|
- self.init_monitors()
|
|
- self.init_logical_monitors()
|
|
+ self.init_monitors(current_state)
|
|
+ self.init_logical_monitors(current_state)
|
|
|
|
- def init_monitors(self):
|
|
+ def init_monitors(self, current_state):
|
|
self.monitors = {}
|
|
- for monitor_variant in self.get_monitors_variant():
|
|
+ for monitor_variant in current_state[1]:
|
|
monitor = Monitor(monitor_variant)
|
|
self.monitors[monitor.connector] = monitor
|
|
|
|
- def init_logical_monitors(self):
|
|
+ def init_logical_monitors(self, current_state):
|
|
self.logical_monitors = []
|
|
- for variant in self.get_logical_monitors_variant():
|
|
+ for variant in current_state[2]:
|
|
logical_monitor = LogicalMonitor.new_from_variant(self, variant)
|
|
self.logical_monitors.append(logical_monitor)
|
|
|
|
- def get_monitors_variant(self):
|
|
- return self.current_state[1]
|
|
-
|
|
- def get_logical_monitors_variant(self):
|
|
- return self.current_state[2]
|
|
-
|
|
- def get_properties_variant(self):
|
|
- return self.current_state[3]
|
|
-
|
|
def print_mode(self, mode, is_last, show_properties, lines):
|
|
print_data(level=2, is_last=is_last, lines=lines, data=f"{mode.name}")
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 964f83f8871829551bad3367975d6b7dc520ed59 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 17 Dec 2024 16:26:19 +0100
|
|
Subject: [PATCH 10/28] gdctl: Introduce and use new named enum
|
|
|
|
The named enum (NamedEnum) is used to describe a mapping for enum values
|
|
to strings. Enums using this define a function that defines the mapping,
|
|
and the named enum handles converting from to strings. This replaces
|
|
existing manually coded translations.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 6750136fd326479f4e4b9b2c84c4d250dfc18826)
|
|
---
|
|
tools/gdctl | 65 ++++++++++++++++++++++++++++++-----------------------
|
|
1 file changed, 37 insertions(+), 28 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 3b47a1c842..1e8b355675 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -12,7 +12,22 @@ INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig"
|
|
|
|
|
|
-class Transform(Enum):
|
|
+class NamedEnum(Enum):
|
|
+ def __str__(self):
|
|
+ return next(
|
|
+ string for enum, string in type(self).enum_names() if enum == self
|
|
+ )
|
|
+
|
|
+ @classmethod
|
|
+ def from_string(cls, string):
|
|
+ return next(
|
|
+ enum
|
|
+ for enum, enum_string in cls.enum_names()
|
|
+ if string == enum_string
|
|
+ )
|
|
+
|
|
+
|
|
+class Transform(NamedEnum):
|
|
NORMAL = 0
|
|
ROTATE_90 = 1
|
|
ROTATE_180 = 2
|
|
@@ -22,36 +37,30 @@ class Transform(Enum):
|
|
ROTATE_270_FLIPPED = 6
|
|
ROTATE_180_FLIPPED = 7
|
|
|
|
- def __str__(self):
|
|
- match self:
|
|
- case self.NORMAL:
|
|
- return "normal"
|
|
- case self.ROTATE_90:
|
|
- return "90"
|
|
- case self.ROTATE_180:
|
|
- return "180"
|
|
- case self.ROTATE_270:
|
|
- return "270"
|
|
- case self.FLIPPED:
|
|
- return "flipped"
|
|
- case self.ROTATE_90_FLIPPED:
|
|
- return "flipped-90"
|
|
- case self.ROTATE_180_FLIPPED:
|
|
- return "flipped-180"
|
|
- case self.ROTATE_270_FLIPPED:
|
|
- return "flipped-270"
|
|
-
|
|
-
|
|
-class LayoutMode(Enum):
|
|
+ @classmethod
|
|
+ def enum_names(cls):
|
|
+ return [
|
|
+ (Transform.NORMAL, "normal"),
|
|
+ (Transform.ROTATE_90, "90"),
|
|
+ (Transform.ROTATE_180, "180"),
|
|
+ (Transform.ROTATE_270, "270"),
|
|
+ (Transform.FLIPPED, "flipped"),
|
|
+ (Transform.ROTATE_90_FLIPPED, "flipped-90"),
|
|
+ (Transform.ROTATE_180_FLIPPED, "flipped-180"),
|
|
+ (Transform.ROTATE_270_FLIPPED, "flipped-270"),
|
|
+ ]
|
|
+
|
|
+
|
|
+class LayoutMode(NamedEnum):
|
|
LOGICAL = 1
|
|
PHYSICAL = 2
|
|
|
|
- def __str__(self):
|
|
- match self:
|
|
- case self.LOGICAL:
|
|
- return "logical"
|
|
- case self.PHYSICAL:
|
|
- return "physical"
|
|
+ @classmethod
|
|
+ def enum_names(cls):
|
|
+ return [
|
|
+ (LayoutMode.LOGICAL, "logical"),
|
|
+ (LayoutMode.PHYSICAL, "physical"),
|
|
+ ]
|
|
|
|
|
|
def translate_property(name, value):
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 71dfeb57ba01459738c64d92302ba29ba6236e56 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 17 Dec 2024 16:53:39 +0100
|
|
Subject: [PATCH 11/28] gdctl: Add support for applying configuration
|
|
|
|
Support defining and applying full configurations, meaning one describes
|
|
the whole configuration with one command, fully replacing the active
|
|
configuration.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit c80134d1ba9fb844c85e7024c3de29f6f50f62d8)
|
|
---
|
|
tools/gdctl | 833 +++++++++++++++++++++++++++++++++++++++++++++++++++-
|
|
1 file changed, 831 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 1e8b355675..005e0b0d40 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
|
from dataclasses import dataclass
|
|
from gi.repository import GLib, Gio
|
|
-from enum import Enum
|
|
+from enum import Enum, Flag
|
|
|
|
NAME = "org.gnome.Mutter.DisplayConfig"
|
|
INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
@@ -63,6 +63,12 @@ class LayoutMode(NamedEnum):
|
|
]
|
|
|
|
|
|
+class ConfigMethod(Enum):
|
|
+ VERIFY = 0
|
|
+ TEMPORARY = 1
|
|
+ PERSISTENT = 2
|
|
+
|
|
+
|
|
def translate_property(name, value):
|
|
enum_properties = {
|
|
"layout-mode": LayoutMode,
|
|
@@ -149,6 +155,32 @@ def strip_dbus_error_prefix(message):
|
|
return message
|
|
|
|
|
|
+def transform_size(size, transform) -> tuple[int, int]:
|
|
+ match transform:
|
|
+ case (
|
|
+ Transform.NORMAL
|
|
+ | Transform.ROTATE_180
|
|
+ | Transform.FLIPPED
|
|
+ | Transform.ROTATE_180_FLIPPED
|
|
+ ):
|
|
+ return size
|
|
+ case (
|
|
+ Transform.ROTATE_90
|
|
+ | Transform.ROTATE_270
|
|
+ | Transform.ROTATE_90_FLIPPED
|
|
+ | Transform.ROTATE_270_FLIPPED
|
|
+ ):
|
|
+ width, height = size
|
|
+ return (height, width)
|
|
+ case _:
|
|
+ raise NotImplementedError
|
|
+
|
|
+
|
|
+def scale_size(size, scale) -> tuple[int, int]:
|
|
+ width, height = size
|
|
+ return (round(width / scale), round(height / scale))
|
|
+
|
|
+
|
|
class DisplayConfig:
|
|
STATE_VARIANT_TYPE = GLib.VariantType.new(
|
|
"(ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv})"
|
|
@@ -176,6 +208,33 @@ class DisplayConfig:
|
|
assert variant.get_type().equal(self.STATE_VARIANT_TYPE)
|
|
return variant
|
|
|
|
+ def apply_monitors_config(self, config, config_method):
|
|
+ serial = config.monitors_state.server_serial
|
|
+ logical_monitors = config.generate_logical_monitor_tuples()
|
|
+ properties = {}
|
|
+
|
|
+ if monitors_state.supports_changing_layout_mode:
|
|
+ properties["layout-mode"] = GLib.Variant(
|
|
+ "u", config.layout_mode.value
|
|
+ )
|
|
+
|
|
+ parameters = GLib.Variant(
|
|
+ "(uua(iiduba(ssa{sv}))a{sv})",
|
|
+ (
|
|
+ serial,
|
|
+ config_method.value,
|
|
+ logical_monitors,
|
|
+ properties,
|
|
+ ),
|
|
+ )
|
|
+ self._proxy.call_sync(
|
|
+ method_name="ApplyMonitorsConfig",
|
|
+ parameters=parameters,
|
|
+ flags=Gio.DBusCallFlags.NO_AUTO_START,
|
|
+ timeout_msec=-1,
|
|
+ cancellable=None,
|
|
+ )
|
|
+
|
|
|
|
@dataclass
|
|
class MonitorMode:
|
|
@@ -215,6 +274,15 @@ class Monitor:
|
|
]
|
|
self.properties = translate_properties(variant[2])
|
|
|
|
+ self.current_mode = next(
|
|
+ (mode for mode in self.modes if "is-current" in mode.properties),
|
|
+ None,
|
|
+ )
|
|
+ self.preferred_mode = next(
|
|
+ (mode for mode in self.modes if "is-preferred" in mode.properties),
|
|
+ None,
|
|
+ )
|
|
+
|
|
self.display_name = self.properties.get("display-name", None)
|
|
|
|
|
|
@@ -227,6 +295,7 @@ class LogicalMonitor:
|
|
transform=Transform.NORMAL,
|
|
is_primary=False,
|
|
properties={},
|
|
+ args=None,
|
|
):
|
|
self.position = position
|
|
self.scale = scale
|
|
@@ -234,6 +303,7 @@ class LogicalMonitor:
|
|
self.is_primary = is_primary
|
|
self.monitors = monitors
|
|
self.properties = properties
|
|
+ self.args = args
|
|
|
|
@classmethod
|
|
def new_from_variant(cls, monitors_state, variant):
|
|
@@ -256,12 +326,498 @@ class LogicalMonitor:
|
|
properties=properties,
|
|
)
|
|
|
|
+ def calculate_size(self, layout_mode):
|
|
+ mode = next(monitor.mode for monitor in self.monitors)
|
|
+ size = transform_size(mode.resolution, self.transform)
|
|
+ match layout_mode:
|
|
+ case LayoutMode.LOGICAL:
|
|
+ return scale_size(size, self.scale)
|
|
+ case LayoutMode.PHYSICAL:
|
|
+ return size
|
|
+
|
|
+ def calculate_right_edge(self, layout_mode):
|
|
+ x, _ = self.position
|
|
+ width, _ = self.calculate_size(layout_mode)
|
|
+ return x + width
|
|
+
|
|
+ def calculate_bottom_edge(self, layout_mode):
|
|
+ _, y = self.position
|
|
+ _, height = self.calculate_size(layout_mode)
|
|
+ return y + height
|
|
+
|
|
+
|
|
+def find_closest_scale(mode, scale) -> float:
|
|
+ @dataclass
|
|
+ class Scale:
|
|
+ scale: float
|
|
+ distance: float
|
|
+
|
|
+ best: Scale | None = None
|
|
+ for supported_scale in mode.supported_scales:
|
|
+ scale_distance = abs(scale - supported_scale)
|
|
+
|
|
+ if scale_distance > 0.1:
|
|
+ continue
|
|
+
|
|
+ if not best or scale_distance < best.distance:
|
|
+ best = Scale(supported_scale, scale_distance)
|
|
+
|
|
+ if not best:
|
|
+ raise ValueError(f"Scale {scale} not supported by mode")
|
|
+
|
|
+ return best.scale
|
|
+
|
|
+
|
|
+def count_keys(dictionary, keys):
|
|
+ in_both = set(keys) & set(dictionary)
|
|
+ return len(in_both)
|
|
+
|
|
+
|
|
+def place_right_of(
|
|
+ logical_monitor: LogicalMonitor,
|
|
+ monitor_mappings: dict,
|
|
+ layout_mode: LayoutMode,
|
|
+ connector: str,
|
|
+ set_y_position: bool,
|
|
+):
|
|
+ connector_logical_monitor = monitor_mappings[connector]
|
|
+ if not connector_logical_monitor.position:
|
|
+ raise ValueError(
|
|
+ f"Logical monitor position configured before {connector} "
|
|
+ )
|
|
+
|
|
+ x = connector_logical_monitor.calculate_right_edge(layout_mode)
|
|
+ if set_y_position:
|
|
+ _, y = connector_logical_monitor.position
|
|
+ else:
|
|
+ y = None
|
|
+
|
|
+ logical_monitor.position = (x, y)
|
|
+
|
|
+
|
|
+def place_left_of(
|
|
+ logical_monitor: LogicalMonitor,
|
|
+ monitor_mappings: dict,
|
|
+ layout_mode: LayoutMode,
|
|
+ connector: str,
|
|
+ set_y_position: bool,
|
|
+):
|
|
+ connector_logical_monitor = monitor_mappings[connector]
|
|
+ if not connector_logical_monitor.position:
|
|
+ raise ValueError(
|
|
+ f"Logical monitor position configured before {connector} "
|
|
+ )
|
|
+
|
|
+ width, _ = logical_monitor.calculate_size(layout_mode)
|
|
+ left_edge, _ = connector_logical_monitor.position
|
|
+ x = left_edge - width
|
|
+
|
|
+ if set_y_position:
|
|
+ _, y = connector_logical_monitor.position
|
|
+ else:
|
|
+ y = None
|
|
+
|
|
+ logical_monitor.position = (x, y)
|
|
+
|
|
+
|
|
+def place_below(
|
|
+ logical_monitor: LogicalMonitor,
|
|
+ monitor_mappings: dict,
|
|
+ layout_mode: LayoutMode,
|
|
+ connector: str,
|
|
+ set_x_position: bool,
|
|
+):
|
|
+ connector_logical_monitor = monitor_mappings[connector]
|
|
+ if not connector_logical_monitor.position:
|
|
+ raise ValueError(
|
|
+ f"Logical monitor position configured before {connector} "
|
|
+ )
|
|
+
|
|
+ y = connector_logical_monitor.calculate_bottom_edge(layout_mode)
|
|
+ if set_x_position:
|
|
+ x, _ = connector_logical_monitor.position
|
|
+ else:
|
|
+ x = logical_monitor.position[0]
|
|
+
|
|
+ logical_monitor.position = (x, y)
|
|
+
|
|
+
|
|
+def place_above(
|
|
+ logical_monitor: LogicalMonitor,
|
|
+ monitor_mappings: dict,
|
|
+ layout_mode: LayoutMode,
|
|
+ connector: str,
|
|
+ set_x_position: bool,
|
|
+):
|
|
+ connector_logical_monitor = monitor_mappings[connector]
|
|
+ if not connector_logical_monitor.position:
|
|
+ raise ValueError(
|
|
+ f"Logical monitor position configured before {connector} "
|
|
+ )
|
|
+
|
|
+ _, height = logical_monitor.calculate_size(layout_mode)
|
|
+ _, top_edge = connector_logical_monitor.position
|
|
+ y = top_edge - height
|
|
+
|
|
+ if set_x_position:
|
|
+ x, _ = connector_logical_monitor.position
|
|
+ else:
|
|
+ x = logical_monitor.position[0]
|
|
+
|
|
+ logical_monitor.position = (x, y)
|
|
+
|
|
+
|
|
+class PositionType(Flag):
|
|
+ NONE = 0
|
|
+ ABSOLUTE_X = 1 << 0
|
|
+ RELATIVE_X = 1 << 1
|
|
+ ABSOLUTE_Y = 1 << 2
|
|
+ RELATIVE_Y = 1 << 3
|
|
+
|
|
+
|
|
+def calculate_position(
|
|
+ logical_monitor: LogicalMonitor,
|
|
+ layout_mode: LayoutMode,
|
|
+ monitor_mappings: dict,
|
|
+):
|
|
+ horizontal_args = count_keys(
|
|
+ logical_monitor.args, ["right_of", "left_of", "x"]
|
|
+ )
|
|
+ vertical_args = count_keys(logical_monitor.args, ["above", "below", "y"])
|
|
+
|
|
+ if horizontal_args > 1:
|
|
+ raise ValueError("Multiple horizontal placement instructions used")
|
|
+ if vertical_args > 1:
|
|
+ raise ValueError("Multiple vertical placement instructions used")
|
|
+
|
|
+ position_types = PositionType.NONE
|
|
+
|
|
+ set_y_position = vertical_args == 0
|
|
+
|
|
+ if "x" in logical_monitor.args:
|
|
+ x = int(logical_monitor.args["x"])
|
|
+ if set_y_position:
|
|
+ y = 0
|
|
+ else:
|
|
+ y = None
|
|
+ logical_monitor.position = (x, y)
|
|
+ position_types |= PositionType.ABSOLUTE_X
|
|
+ elif "right_of" in logical_monitor.args:
|
|
+ connector = logical_monitor.args["right_of"]
|
|
+ if connector not in monitor_mappings:
|
|
+ raise ValueError(
|
|
+ f"Invalid connector {connector} passed to --right-of"
|
|
+ )
|
|
+ place_right_of(
|
|
+ logical_monitor,
|
|
+ monitor_mappings,
|
|
+ layout_mode,
|
|
+ connector,
|
|
+ set_y_position,
|
|
+ )
|
|
+ position_types |= PositionType.RELATIVE_X
|
|
+ elif "left_of" in logical_monitor.args:
|
|
+ connector = logical_monitor.args["left_of"]
|
|
+ if connector not in monitor_mappings:
|
|
+ raise ValueError(
|
|
+ f"Invalid connector {connector} passed to --left-of"
|
|
+ )
|
|
+ place_left_of(
|
|
+ logical_monitor,
|
|
+ monitor_mappings,
|
|
+ layout_mode,
|
|
+ connector,
|
|
+ set_y_position,
|
|
+ )
|
|
+ position_types |= PositionType.RELATIVE_X
|
|
+ else:
|
|
+ logical_monitor.position = (0, 0)
|
|
+
|
|
+ set_x_position = horizontal_args == 0
|
|
+
|
|
+ if "y" in logical_monitor.args:
|
|
+ y = int(logical_monitor.args["y"])
|
|
+ if set_x_position:
|
|
+ x = 0
|
|
+ else:
|
|
+ x = logical_monitor.position[0]
|
|
+ logical_monitor.position = (x, y)
|
|
+ position_types |= PositionType.ABSOLUTE_Y
|
|
+ elif "below" in logical_monitor.args:
|
|
+ connector = logical_monitor.args["below"]
|
|
+ if connector not in monitor_mappings:
|
|
+ raise ValueError(f"Invalid connector {connector} passed to --below")
|
|
+ place_below(
|
|
+ logical_monitor,
|
|
+ monitor_mappings,
|
|
+ layout_mode,
|
|
+ connector,
|
|
+ set_x_position,
|
|
+ )
|
|
+ position_types |= PositionType.RELATIVE_Y
|
|
+ elif "above" in logical_monitor.args:
|
|
+ connector = logical_monitor.args["above"]
|
|
+ if connector not in monitor_mappings:
|
|
+ raise ValueError(f"Invalid connector {connector} passed to --above")
|
|
+ place_above(
|
|
+ logical_monitor,
|
|
+ monitor_mappings,
|
|
+ layout_mode,
|
|
+ connector,
|
|
+ set_x_position,
|
|
+ )
|
|
+ position_types |= PositionType.RELATIVE_Y
|
|
+ else:
|
|
+ x, y = logical_monitor.position
|
|
+ if not y:
|
|
+ y = 0
|
|
+ logical_monitor.position = (x, y)
|
|
+
|
|
+ assert logical_monitor.position[0] is not None
|
|
+ assert logical_monitor.position[1] is not None
|
|
+
|
|
+ return position_types
|
|
+
|
|
+
|
|
+def align_horizontally(logical_monitors: list[LogicalMonitor]):
|
|
+ min_x = min(
|
|
+ logical_monitor.position[0] for logical_monitor in logical_monitors
|
|
+ )
|
|
+
|
|
+ dx = min_x
|
|
+ if dx == 0:
|
|
+ return
|
|
+
|
|
+ for logical_monitor in logical_monitors:
|
|
+ x, y = logical_monitor.position
|
|
+ logical_monitor.position = (x - dx, y)
|
|
+
|
|
+
|
|
+def align_vertically(logical_monitors: list[LogicalMonitor]):
|
|
+ min_y = min(
|
|
+ logical_monitor.position[1] for logical_monitor in logical_monitors
|
|
+ )
|
|
+
|
|
+ dy = min_y
|
|
+ if dy == 0:
|
|
+ return
|
|
+
|
|
+ for logical_monitor in logical_monitors:
|
|
+ x, y = logical_monitor.position
|
|
+ logical_monitor.position = (x, y - dy)
|
|
+
|
|
+
|
|
+def calculate_positions(
|
|
+ logical_monitors: list[LogicalMonitor],
|
|
+ layout_mode: LayoutMode,
|
|
+ monitor_mappings: dict,
|
|
+):
|
|
+ position_types = PositionType.NONE
|
|
+ for logical_monitor in logical_monitors:
|
|
+ position_types |= calculate_position(
|
|
+ logical_monitor, layout_mode, monitor_mappings
|
|
+ )
|
|
+
|
|
+ if not position_types & PositionType.ABSOLUTE_X:
|
|
+ align_horizontally(logical_monitors)
|
|
+ if not position_types & PositionType.ABSOLUTE_Y:
|
|
+ align_vertically(logical_monitors)
|
|
+
|
|
+
|
|
+def create_logical_monitor(monitors_state, layout_mode, logical_monitor_args):
|
|
+ if "monitors" not in logical_monitor_args:
|
|
+ raise ValueError("Logical monitor empty")
|
|
+ monitors_arg = logical_monitor_args["monitors"]
|
|
+
|
|
+ scale = logical_monitor_args.get("scale", None)
|
|
+ is_primary = logical_monitor_args.get("primary", False)
|
|
+ transform = Transform.from_string(
|
|
+ logical_monitor_args.get("transform", "normal")
|
|
+ )
|
|
+
|
|
+ monitors = []
|
|
+
|
|
+ common_mode_resolution = None
|
|
+
|
|
+ for monitor_args in monitors_arg:
|
|
+ (connector,) = monitor_args["key"]
|
|
+ if connector not in monitors_state.monitors:
|
|
+ raise ValueError(f"Monitor {connector} not found")
|
|
+ monitor = monitors_state.monitors[connector]
|
|
+
|
|
+ mode_name = monitor_args.get("mode", None)
|
|
+ if mode_name:
|
|
+ mode = next(
|
|
+ (mode for mode in monitor.modes if mode.name == mode_name), None
|
|
+ )
|
|
+ if not mode:
|
|
+ raise ValueError(
|
|
+ f"No mode {mode_name} available for {connector}"
|
|
+ )
|
|
+ else:
|
|
+ mode = monitor.preferred_mode
|
|
+
|
|
+ if not common_mode_resolution:
|
|
+ common_mode_resolution = mode.resolution
|
|
+
|
|
+ if not scale:
|
|
+ scale = mode.preferred_scale
|
|
+ else:
|
|
+ scale = find_closest_scale(mode, scale)
|
|
+ else:
|
|
+ mode_width, mode_height = mode.resolution
|
|
+ common_mode_width, common_mode_height = common_mode_resolution
|
|
+ if (
|
|
+ mode_width != common_mode_width
|
|
+ or mode_height != common_mode_height
|
|
+ ):
|
|
+ raise ValueError(
|
|
+ "Different monitor resolutions within the same logical monitor"
|
|
+ )
|
|
+
|
|
+ monitor.mode = mode
|
|
+
|
|
+ monitors.append(monitor)
|
|
+
|
|
+ return LogicalMonitor(
|
|
+ monitors_state,
|
|
+ monitors,
|
|
+ scale,
|
|
+ is_primary=is_primary,
|
|
+ transform=transform,
|
|
+ position=None,
|
|
+ args=logical_monitor_args,
|
|
+ )
|
|
+
|
|
+
|
|
+def generate_configuration(monitors_state, args):
|
|
+ layout_mode_str = args.layout_mode
|
|
+ if not layout_mode_str:
|
|
+ layout_mode = monitors_state.layout_mode
|
|
+ else:
|
|
+ if not monitors_state.supports_changing_layout_mode:
|
|
+ raise ValueError(
|
|
+ "Configuring layout mode not supported by the server"
|
|
+ )
|
|
+ layout_mode = LayoutMode.from_string(layout_mode_str)
|
|
+
|
|
+ logical_monitors = []
|
|
+ monitor_mappings = {}
|
|
+ for logical_monitor_args in args.logical_monitors:
|
|
+ logical_monitor = create_logical_monitor(
|
|
+ monitors_state, layout_mode, logical_monitor_args
|
|
+ )
|
|
+ logical_monitors.append(logical_monitor)
|
|
+ for monitor in logical_monitor.monitors:
|
|
+ monitor_mappings[monitor.connector] = logical_monitor
|
|
+
|
|
+ calculate_positions(logical_monitors, layout_mode, monitor_mappings)
|
|
+
|
|
+ return Config(monitors_state, logical_monitors, layout_mode)
|
|
+
|
|
+
|
|
+def derive_config_method(args):
|
|
+ if args.persistent and args.verify:
|
|
+ raise ValueError(
|
|
+ "Configuration can't be both persistent and verify-only"
|
|
+ )
|
|
+ if args.persistent:
|
|
+ return ConfigMethod.PERSISTENT
|
|
+ elif args.verify:
|
|
+ return ConfigMethod.VERIFY
|
|
+ else:
|
|
+ return ConfigMethod.TEMPORARY
|
|
+
|
|
+
|
|
+def print_config(config):
|
|
+ print("Configuration:")
|
|
+ lines = []
|
|
+
|
|
+ print_data(
|
|
+ level=0,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Layout mode: {config.layout_mode}",
|
|
+ )
|
|
+
|
|
+ print_data(
|
|
+ level=0,
|
|
+ is_last=True,
|
|
+ lines=lines,
|
|
+ data=f"Logical monitors ({len(config.logical_monitors)})",
|
|
+ )
|
|
+
|
|
+ index = 1
|
|
+ for logical_monitor in config.logical_monitors:
|
|
+ is_last = logical_monitor == config.logical_monitors[-1]
|
|
+ print_data(
|
|
+ level=1,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data=f"Logical monitor #{index}",
|
|
+ )
|
|
+
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Position: {logical_monitor.position}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Scale: {logical_monitor.scale}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Transform: {logical_monitor.transform}",
|
|
+ )
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=False,
|
|
+ lines=lines,
|
|
+ data=f"Primary: {'yes' if logical_monitor.is_primary else 'no'}",
|
|
+ )
|
|
+
|
|
+ print_data(
|
|
+ level=2,
|
|
+ is_last=True,
|
|
+ lines=lines,
|
|
+ data=f"Monitors: ({len(logical_monitor.monitors)})",
|
|
+ )
|
|
+ for monitor in logical_monitor.monitors:
|
|
+ is_last = monitor == logical_monitor.monitors[-1]
|
|
+ print_data(
|
|
+ level=3,
|
|
+ is_last=is_last,
|
|
+ lines=lines,
|
|
+ data=f"Monitor {monitor.connector} ({monitor.display_name})",
|
|
+ )
|
|
+ print_data(
|
|
+ level=4,
|
|
+ is_last=True,
|
|
+ lines=lines,
|
|
+ data=f"Mode: {monitor.mode.name}",
|
|
+ )
|
|
+
|
|
+ index += 1
|
|
+
|
|
|
|
class MonitorsState:
|
|
def __init__(self, display_config):
|
|
current_state = display_config.get_current_state()
|
|
|
|
+ self.server_serial = current_state[0]
|
|
self.properties = translate_properties(current_state[3])
|
|
+ self.supports_changing_layout_mode = self.properties.get(
|
|
+ "supports-changing-layout-mode", False
|
|
+ )
|
|
+ self.layout_mode = (
|
|
+ self.properties.get("layout-mode") or LayoutMode.LOGICAL
|
|
+ )
|
|
|
|
self.init_monitors(current_state)
|
|
self.init_logical_monitors(current_state)
|
|
@@ -278,6 +834,9 @@ class MonitorsState:
|
|
logical_monitor = LogicalMonitor.new_from_variant(self, variant)
|
|
self.logical_monitors.append(logical_monitor)
|
|
|
|
+ def create_current_config(self):
|
|
+ return Config.create_current(self)
|
|
+
|
|
def print_mode(self, mode, is_last, show_properties, lines):
|
|
print_data(level=2, is_last=is_last, lines=lines, data=f"{mode.name}")
|
|
|
|
@@ -472,8 +1031,109 @@ class MonitorsState:
|
|
print_properties(level=-1, lines=lines, properties=properties)
|
|
|
|
|
|
+@dataclass
|
|
+class Config:
|
|
+ monitors_state: MonitorsState
|
|
+ logical_monitors: list[LogicalMonitor]
|
|
+ layout_mode: LayoutMode
|
|
+
|
|
+ def generate_monitor_tuples(self, monitors):
|
|
+ return [
|
|
+ # Variant type: (ssa{sv})
|
|
+ (
|
|
+ monitor.connector,
|
|
+ monitor.mode.name,
|
|
+ {},
|
|
+ )
|
|
+ for monitor in monitors
|
|
+ ]
|
|
+
|
|
+ def generate_logical_monitor_tuples(self):
|
|
+ tuples = []
|
|
+ for logical_monitor in self.logical_monitors:
|
|
+ x, y = logical_monitor.position
|
|
+ scale = logical_monitor.scale
|
|
+ transform = logical_monitor.transform.value
|
|
+ is_primary = logical_monitor.is_primary
|
|
+
|
|
+ monitors = self.generate_monitor_tuples(logical_monitor.monitors)
|
|
+
|
|
+ # Variant type: (iiduba(ssa{sv}))
|
|
+ tuples.append(
|
|
+ (
|
|
+ x,
|
|
+ y,
|
|
+ scale,
|
|
+ transform,
|
|
+ is_primary,
|
|
+ monitors,
|
|
+ )
|
|
+ )
|
|
+ return tuples
|
|
+
|
|
+
|
|
+class GroupAction(argparse.Action):
|
|
+ def __call__(self, parser, namespace, values, option_string=None):
|
|
+ namespace._current_group = {}
|
|
+ groups = namespace.__dict__.setdefault(self.dest, [])
|
|
+ groups.append(namespace._current_group)
|
|
+
|
|
+
|
|
+class SubGroupAction(argparse.Action):
|
|
+ def __call__(self, parser, namespace, values, option_string=None):
|
|
+ if not hasattr(namespace, "_current_group"):
|
|
+ raise argparse.ArgumentError(
|
|
+ self, "No current group to add sub-group to"
|
|
+ )
|
|
+ if self.dest not in namespace._current_group:
|
|
+ namespace._current_group[self.dest] = []
|
|
+ sub_group = {
|
|
+ "key": values,
|
|
+ }
|
|
+ namespace._current_group[self.dest].append(sub_group)
|
|
+ namespace._current_sub_group = sub_group
|
|
+
|
|
+
|
|
+class AppendToGlobal(argparse.Action):
|
|
+ def __call__(self, parser, namespace, values, option_string=None):
|
|
+ if getattr(namespace, "_current_group", None) is not None:
|
|
+ raise argparse.ArgumentError(self, "Must pass during global scope")
|
|
+ setattr(namespace, self.dest, self.const or values)
|
|
+
|
|
+
|
|
+class AppendToGroup(argparse.Action):
|
|
+ def __call__(self, parser, namespace, values, option_string=None):
|
|
+ if getattr(namespace, "_current_group", None) is None:
|
|
+ raise argparse.ArgumentError(self, "No current group to add to")
|
|
+ namespace._current_group[self.dest] = self.const or values
|
|
+
|
|
+
|
|
+class AppendToSubGroup(argparse.Action):
|
|
+ def __call__(self, parser, namespace, values, option_string=None):
|
|
+ if getattr(namespace, "_current_group", None) is None:
|
|
+ raise argparse.ArgumentError(self, "No current group")
|
|
+ if getattr(namespace, "_current_sub_group", None) is None:
|
|
+ raise argparse.ArgumentError(self, "No current sub-group")
|
|
+ namespace._current_sub_group[self.dest] = self.const or values
|
|
+
|
|
+
|
|
+def clearattr(namespace, attr):
|
|
+ if hasattr(namespace, attr):
|
|
+ delattr(namespace, attr)
|
|
+
|
|
+
|
|
+class GdctlParser(argparse.ArgumentParser):
|
|
+ def parse_args(self):
|
|
+ namespace = super().parse_args()
|
|
+ clearattr(namespace, "_current_group")
|
|
+ clearattr(namespace, "_current_sub_group")
|
|
+ return namespace
|
|
+
|
|
+
|
|
if __name__ == "__main__":
|
|
- parser = argparse.ArgumentParser(description="Display control utility")
|
|
+ parser = GdctlParser(
|
|
+ description="Display control utility",
|
|
+ )
|
|
|
|
subparser = parser.add_subparsers(
|
|
dest="command",
|
|
@@ -502,6 +1162,140 @@ if __name__ == "__main__":
|
|
action="store_true",
|
|
help="Display all available information",
|
|
)
|
|
+ set_parser = subparser.add_parser(
|
|
+ "set",
|
|
+ help="Set display configuration",
|
|
+ )
|
|
+ set_parser.add_argument(
|
|
+ "-P",
|
|
+ "--persistent",
|
|
+ action=AppendToGlobal,
|
|
+ const=True,
|
|
+ nargs=0,
|
|
+ default=False,
|
|
+ )
|
|
+ set_parser.add_argument(
|
|
+ "-v",
|
|
+ "--verbose",
|
|
+ action=AppendToGlobal,
|
|
+ const=True,
|
|
+ nargs=0,
|
|
+ default=False,
|
|
+ )
|
|
+ set_parser.add_argument(
|
|
+ "-V",
|
|
+ "--verify",
|
|
+ action=AppendToGlobal,
|
|
+ const=True,
|
|
+ nargs=0,
|
|
+ default=False,
|
|
+ )
|
|
+ set_parser.add_argument(
|
|
+ "-l",
|
|
+ "--layout-mode",
|
|
+ choices=[str(layout_mode) for layout_mode in list(LayoutMode)],
|
|
+ type=str,
|
|
+ action=AppendToGlobal,
|
|
+ )
|
|
+ set_parser.add_argument(
|
|
+ "-L",
|
|
+ "--logical-monitor",
|
|
+ dest="logical_monitors",
|
|
+ action=GroupAction,
|
|
+ nargs=0,
|
|
+ default=[],
|
|
+ )
|
|
+ logical_monitor_parser = set_parser.add_argument_group(
|
|
+ "logical_monitor",
|
|
+ "Logical monitor options (pass after --logical-monitor)",
|
|
+ argument_default=argparse.SUPPRESS,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "-M",
|
|
+ "--monitor",
|
|
+ dest="monitors",
|
|
+ metavar="CONNECTOR",
|
|
+ action=SubGroupAction,
|
|
+ help="Configure monitor",
|
|
+ )
|
|
+ monitor_parser = set_parser.add_argument_group(
|
|
+ "monitor",
|
|
+ "Monitor options (pass after --monitor)",
|
|
+ argument_default=argparse.SUPPRESS,
|
|
+ )
|
|
+ monitor_parser.add_argument(
|
|
+ "--mode",
|
|
+ "-m",
|
|
+ action=AppendToSubGroup,
|
|
+ help="Monitor mode",
|
|
+ type=str,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--primary",
|
|
+ "-p",
|
|
+ action=AppendToGroup,
|
|
+ help="Mark as primary",
|
|
+ type=bool,
|
|
+ const=True,
|
|
+ nargs=0,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--scale",
|
|
+ "-s",
|
|
+ action=AppendToGroup,
|
|
+ help="Logical monitor scale",
|
|
+ type=float,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--transform",
|
|
+ "-t",
|
|
+ action=AppendToGroup,
|
|
+ help="Apply viewport transform",
|
|
+ choices=[str(transform) for transform in list(Transform)],
|
|
+ type=str,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--x",
|
|
+ "-x",
|
|
+ action=AppendToGroup,
|
|
+ help="X position",
|
|
+ type=int,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--y",
|
|
+ "-y",
|
|
+ action=AppendToGroup,
|
|
+ help="Y position",
|
|
+ type=int,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--right-of",
|
|
+ action=AppendToGroup,
|
|
+ metavar="CONNECTOR",
|
|
+ help="Place right of other monitor",
|
|
+ type=str,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--left-of",
|
|
+ action=AppendToGroup,
|
|
+ metavar="CONNECTOR",
|
|
+ help="Place left of other monitor",
|
|
+ type=str,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--above",
|
|
+ action=AppendToGroup,
|
|
+ metavar="CONNECTOR",
|
|
+ help="Place above other monitor",
|
|
+ type=str,
|
|
+ )
|
|
+ logical_monitor_parser.add_argument(
|
|
+ "--below",
|
|
+ action=AppendToGroup,
|
|
+ metavar="CONNECTOR",
|
|
+ help="Place below other monitor",
|
|
+ type=str,
|
|
+ )
|
|
|
|
args = parser.parse_args()
|
|
|
|
@@ -530,3 +1324,38 @@ if __name__ == "__main__":
|
|
show_modes=show_modes,
|
|
show_properties=show_properties,
|
|
)
|
|
+ case "set":
|
|
+ try:
|
|
+ display_config = DisplayConfig()
|
|
+ monitors_state = MonitorsState(display_config)
|
|
+ except GLib.Error as e:
|
|
+ if e.domain == GLib.quark_to_string(Gio.DBusError.quark()):
|
|
+ error_message = strip_dbus_error_prefix(e.message)
|
|
+ print(
|
|
+ f"Failed retrieve current state: {error_message}",
|
|
+ file=sys.stderr,
|
|
+ )
|
|
+ sys.exit(1)
|
|
+
|
|
+ try:
|
|
+ config = generate_configuration(monitors_state, args)
|
|
+ config_method = derive_config_method(args)
|
|
+ if args.verbose:
|
|
+ print_config(config)
|
|
+ display_config.apply_monitors_config(config, config_method)
|
|
+ except ValueError as e:
|
|
+ print(f"Failed to create configuration: {e}", file=sys.stderr)
|
|
+ sys.exit(1)
|
|
+ except GLib.Error as e:
|
|
+ if e.domain == GLib.quark_to_string(Gio.DBusError.quark()):
|
|
+ error_message = strip_dbus_error_prefix(e.message)
|
|
+ print(
|
|
+ f"Failed to apply configuration: {error_message}",
|
|
+ file=sys.stderr,
|
|
+ )
|
|
+ else:
|
|
+ print(
|
|
+ f"Failed to apply configuration: {e.message}",
|
|
+ file=sys.stderr,
|
|
+ )
|
|
+ sys.exit(1)
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 63ccfe3d8492c01a8a047372f78e094208869641 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 00:09:17 +0100
|
|
Subject: [PATCH 12/28] tests/build: Allow passing commandline arguments to
|
|
tests
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 7a39f05b6983be2cd1635ad50341d0131952e7eb)
|
|
---
|
|
src/tests/generic.test.in | 2 +-
|
|
src/tests/meson.build | 4 ++++
|
|
2 files changed, 5 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/src/tests/generic.test.in b/src/tests/generic.test.in
|
|
index 1913544638..623e51f24d 100644
|
|
--- a/src/tests/generic.test.in
|
|
+++ b/src/tests/generic.test.in
|
|
@@ -1,5 +1,5 @@
|
|
[Test]
|
|
Description=Mutter test: @testname@
|
|
-Exec=sh -ec 'env G_TEST_SRCDIR=@pkgdatadir@ G_TEST_BUILDDIR=@libexecdir@/installed-tests/mutter-@apiversion@ MUTTER_REF_TEST_RESULT_DIR=@reftestresultdir@ @libexecdir@/installed-tests/mutter-@apiversion@/mutter-installed-dbus-session.py @runnerargs@ -- @libexecdir@/installed-tests/mutter-@apiversion@/@testexecutable@'
|
|
+Exec=sh -ec 'env G_TEST_SRCDIR=@pkgdatadir@ G_TEST_BUILDDIR=@libexecdir@/installed-tests/mutter-@apiversion@ MUTTER_REF_TEST_RESULT_DIR=@reftestresultdir@ @libexecdir@/installed-tests/mutter-@apiversion@/mutter-installed-dbus-session.py @runnerargs@ -- @libexecdir@/installed-tests/mutter-@apiversion@/@testexecutable@ @testargs@'
|
|
Type=session
|
|
Output=TAP
|
|
diff --git a/src/tests/meson.build b/src/tests/meson.build
|
|
index 79e7d48a31..9f1fc1157b 100644
|
|
--- a/src/tests/meson.build
|
|
+++ b/src/tests/meson.build
|
|
@@ -792,8 +792,11 @@ foreach test_case: test_cases
|
|
|
|
test_depends = [ default_plugin ] + test_case.get('depends', [])
|
|
|
|
+ args = test_case.get('args', [])
|
|
+
|
|
test(test_case['name'], test_executable,
|
|
suite: ['core', 'mutter/' + test_case['suite']],
|
|
+ args: args,
|
|
env: test_env,
|
|
depends: test_depends,
|
|
is_parallel: false,
|
|
@@ -1043,6 +1046,7 @@ if have_installed_tests
|
|
installed_tests_cdata.set('reftestresultdir', '/tmp/mutter-ref-test-results')
|
|
installed_tests_cdata.set('testname', test_case['name'])
|
|
installed_tests_cdata.set('testexecutable', 'mutter-' + test_case['name'])
|
|
+ installed_tests_cdata.set('testargs', ' '.join(test_case.get('args', [])))
|
|
installed_tests_cdata.set('runnerargs', ' '.join(runner_args))
|
|
|
|
configure_file(
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From ccf5cd267e2990f400567f23c4b06295434fc68c Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 00:15:27 +0100
|
|
Subject: [PATCH 13/28] gdctl: Install to bin/
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit a9d8aaa6fd4ae6f9a7371eb304197118d10888f6)
|
|
---
|
|
tools/meson.build | 5 +++++
|
|
1 file changed, 5 insertions(+)
|
|
|
|
diff --git a/tools/meson.build b/tools/meson.build
|
|
index 83e98721cb..2603ed3d87 100644
|
|
--- a/tools/meson.build
|
|
+++ b/tools/meson.build
|
|
@@ -1 +1,6 @@
|
|
+install_data(
|
|
+ 'gdctl',
|
|
+ install_dir: bindir,
|
|
+)
|
|
+
|
|
get_state_tool = find_program('get-state.py')
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 3510d6afc7698845cbb4fd5ed13e4e7f901abd75 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 00:16:43 +0100
|
|
Subject: [PATCH 14/28] tests: Add get state D-Bus test using gdctl
|
|
|
|
Test is ref test like, with gdctl outputs being compared.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 1cbfc07df0c90d68a3e629afe10056e277c8e6f4)
|
|
---
|
|
src/tests/gdctl/show | 29 ++++
|
|
src/tests/gdctl/show-modes | 35 ++++
|
|
src/tests/gdctl/show-properties | 57 ++++++
|
|
src/tests/gdctl/show-verbose | 93 ++++++++++
|
|
src/tests/generic.test.in | 2 +-
|
|
src/tests/meson.build | 12 ++
|
|
src/tests/monitor-dbus-tests.c | 298 ++++++++++++++++++++++++++++++++
|
|
tools/meson.build | 1 +
|
|
8 files changed, 526 insertions(+), 1 deletion(-)
|
|
create mode 100644 src/tests/gdctl/show
|
|
create mode 100644 src/tests/gdctl/show-modes
|
|
create mode 100644 src/tests/gdctl/show-properties
|
|
create mode 100644 src/tests/gdctl/show-verbose
|
|
create mode 100644 src/tests/monitor-dbus-tests.c
|
|
|
|
diff --git a/src/tests/gdctl/show b/src/tests/gdctl/show
|
|
new file mode 100644
|
|
index 0000000000..62f9c0ba5c
|
|
--- /dev/null
|
|
+++ b/src/tests/gdctl/show
|
|
@@ -0,0 +1,29 @@
|
|
+Monitors:
|
|
+├──Monitor DP-1 (MetaProduct's Inc. 14")
|
|
+│ ├──Vendor: MetaProduct's Inc.
|
|
+│ ├──Product: MetaMonitor
|
|
+│ ├──Serial: 0x1234560
|
|
+│ └──Current mode
|
|
+│ └──3840x2160@60.000
|
|
+└──Monitor DP-2 (MetaProduct's Inc. 13")
|
|
+ ├──Vendor: MetaProduct's Inc.
|
|
+ ├──Product: MetaMonitor
|
|
+ ├──Serial: 0x1234561
|
|
+ └──Current mode
|
|
+ └──2560x1440@60.000
|
|
+
|
|
+Logical monitors:
|
|
+├──Logical monitor #1
|
|
+│ ├──Position: (0, 0)
|
|
+│ ├──Scale: 2.2018349170684814
|
|
+│ ├──Transform: normal
|
|
+│ ├──Primary: yes
|
|
+│ └──Monitors: (1)
|
|
+│ └──DP-1 (MetaProduct's Inc. 14")
|
|
+└──Logical monitor #2
|
|
+ ├──Position: (1744, 0)
|
|
+ ├──Scale: 1.7582417726516724
|
|
+ ├──Transform: normal
|
|
+ ├──Primary: no
|
|
+ └──Monitors: (1)
|
|
+ └──DP-2 (MetaProduct's Inc. 13")
|
|
diff --git a/src/tests/gdctl/show-modes b/src/tests/gdctl/show-modes
|
|
new file mode 100644
|
|
index 0000000000..b175e1fed9
|
|
--- /dev/null
|
|
+++ b/src/tests/gdctl/show-modes
|
|
@@ -0,0 +1,35 @@
|
|
+Monitors:
|
|
+├──Monitor DP-1 (MetaProduct's Inc. 14")
|
|
+│ ├──Vendor: MetaProduct's Inc.
|
|
+│ ├──Product: MetaMonitor
|
|
+│ ├──Serial: 0x1234560
|
|
+│ └──Modes (4)
|
|
+│ ├──3840x2160@60.000
|
|
+│ ├──3840x2160@30.000
|
|
+│ ├──2560x1440@60.000
|
|
+│ └──1440x900@60.000
|
|
+└──Monitor DP-2 (MetaProduct's Inc. 13")
|
|
+ ├──Vendor: MetaProduct's Inc.
|
|
+ ├──Product: MetaMonitor
|
|
+ ├──Serial: 0x1234561
|
|
+ └──Modes (4)
|
|
+ ├──2560x1440@60.000
|
|
+ ├──1440x900@60.000
|
|
+ ├──1366x768@60.000
|
|
+ └──800x600@60.000
|
|
+
|
|
+Logical monitors:
|
|
+├──Logical monitor #1
|
|
+│ ├──Position: (0, 0)
|
|
+│ ├──Scale: 2.2018349170684814
|
|
+│ ├──Transform: normal
|
|
+│ ├──Primary: yes
|
|
+│ └──Monitors: (1)
|
|
+│ └──DP-1 (MetaProduct's Inc. 14")
|
|
+└──Logical monitor #2
|
|
+ ├──Position: (1744, 0)
|
|
+ ├──Scale: 1.7582417726516724
|
|
+ ├──Transform: normal
|
|
+ ├──Primary: no
|
|
+ └──Monitors: (1)
|
|
+ └──DP-2 (MetaProduct's Inc. 13")
|
|
diff --git a/src/tests/gdctl/show-properties b/src/tests/gdctl/show-properties
|
|
new file mode 100644
|
|
index 0000000000..d8c7aa03de
|
|
--- /dev/null
|
|
+++ b/src/tests/gdctl/show-properties
|
|
@@ -0,0 +1,57 @@
|
|
+Monitors:
|
|
+├──Monitor DP-1 (MetaProduct's Inc. 14")
|
|
+│ ├──Vendor: MetaProduct's Inc.
|
|
+│ ├──Product: MetaMonitor
|
|
+│ ├──Serial: 0x1234560
|
|
+│ ├──Current mode
|
|
+│ │ └──3840x2160@60.000
|
|
+│ │ ├──Dimension: 3840x2160
|
|
+│ │ ├──Refresh rate: 60.000
|
|
+│ │ ├──Preferred scale: 2.2018349170684814
|
|
+│ │ ├──Supported scales: [1.0, 1.25, 1.5, 1.7518248558044434, 2.0, 2.2018349170684814, 2.5, 2.7586207389831543, 3.0, 3.2432432174682617, 3.4782607555389404, 3.75, 4.0]
|
|
+│ │ └──Properties: (2)
|
|
+│ │ ├──is-current ⇒ yes
|
|
+│ │ └──is-preferred ⇒ yes
|
|
+│ └──Properties: (3)
|
|
+│ ├──is-builtin ⇒ no
|
|
+│ ├──display-name ⇒ MetaProduct's Inc. 14"
|
|
+│ └──is-for-lease ⇒ no
|
|
+└──Monitor DP-2 (MetaProduct's Inc. 13")
|
|
+ ├──Vendor: MetaProduct's Inc.
|
|
+ ├──Product: MetaMonitor
|
|
+ ├──Serial: 0x1234561
|
|
+ ├──Current mode
|
|
+ │ └──2560x1440@60.000
|
|
+ │ ├──Dimension: 2560x1440
|
|
+ │ ├──Refresh rate: 60.000
|
|
+ │ ├──Preferred scale: 1.7582417726516724
|
|
+ │ ├──Supported scales: [1.0, 1.25, 1.495327115058899, 1.7582417726516724, 2.0, 2.253521203994751, 2.5, 2.7586207389831543, 3.0188679695129395]
|
|
+ │ └──Properties: (2)
|
|
+ │ ├──is-current ⇒ yes
|
|
+ │ └──is-preferred ⇒ yes
|
|
+ └──Properties: (3)
|
|
+ ├──is-builtin ⇒ no
|
|
+ ├──display-name ⇒ MetaProduct's Inc. 13"
|
|
+ └──is-for-lease ⇒ no
|
|
+
|
|
+Logical monitors:
|
|
+├──Logical monitor #1
|
|
+│ ├──Position: (0, 0)
|
|
+│ ├──Scale: 2.2018349170684814
|
|
+│ ├──Transform: normal
|
|
+│ ├──Primary: yes
|
|
+│ ├──Monitors: (1)
|
|
+│ │ └──DP-1 (MetaProduct's Inc. 14")
|
|
+│ └──Properties: (0)
|
|
+└──Logical monitor #2
|
|
+ ├──Position: (1744, 0)
|
|
+ ├──Scale: 1.7582417726516724
|
|
+ ├──Transform: normal
|
|
+ ├──Primary: no
|
|
+ ├──Monitors: (1)
|
|
+ │ └──DP-2 (MetaProduct's Inc. 13")
|
|
+ └──Properties: (0)
|
|
+
|
|
+Properties: (2)
|
|
+├──layout-mode ⇒ logical
|
|
+└──supports-changing-layout-mode ⇒ yes
|
|
diff --git a/src/tests/gdctl/show-verbose b/src/tests/gdctl/show-verbose
|
|
new file mode 100644
|
|
index 0000000000..c88a0a8e35
|
|
--- /dev/null
|
|
+++ b/src/tests/gdctl/show-verbose
|
|
@@ -0,0 +1,93 @@
|
|
+Monitors:
|
|
+├──Monitor DP-1 (MetaProduct's Inc. 14")
|
|
+│ ├──Vendor: MetaProduct's Inc.
|
|
+│ ├──Product: MetaMonitor
|
|
+│ ├──Serial: 0x1234560
|
|
+│ ├──Modes (4)
|
|
+│ │ ├──3840x2160@60.000
|
|
+│ │ │ ├──Dimension: 3840x2160
|
|
+│ │ │ ├──Refresh rate: 60.000
|
|
+│ │ │ ├──Preferred scale: 2.2018349170684814
|
|
+│ │ │ ├──Supported scales: [1.0, 1.25, 1.5, 1.7518248558044434, 2.0, 2.2018349170684814, 2.5, 2.7586207389831543, 3.0, 3.2432432174682617, 3.4782607555389404, 3.75, 4.0]
|
|
+│ │ │ └──Properties: (2)
|
|
+│ │ │ ├──is-current ⇒ yes
|
|
+│ │ │ └──is-preferred ⇒ yes
|
|
+│ │ ├──3840x2160@30.000
|
|
+│ │ │ ├──Dimension: 3840x2160
|
|
+│ │ │ ├──Refresh rate: 30.000
|
|
+│ │ │ ├──Preferred scale: 2.2018349170684814
|
|
+│ │ │ ├──Supported scales: [1.0, 1.25, 1.5, 1.7518248558044434, 2.0, 2.2018349170684814, 2.5, 2.7586207389831543, 3.0, 3.2432432174682617, 3.4782607555389404, 3.75, 4.0]
|
|
+│ │ │ └──Properties: (0)
|
|
+│ │ ├──2560x1440@60.000
|
|
+│ │ │ ├──Dimension: 2560x1440
|
|
+│ │ │ ├──Refresh rate: 60.000
|
|
+│ │ │ ├──Preferred scale: 1.495327115058899
|
|
+│ │ │ ├──Supported scales: [1.0, 1.25, 1.495327115058899, 1.7582417726516724, 2.0, 2.253521203994751, 2.5, 2.7586207389831543, 3.0188679695129395]
|
|
+│ │ │ └──Properties: (0)
|
|
+│ │ └──1440x900@60.000
|
|
+│ │ ├──Dimension: 1440x900
|
|
+│ │ ├──Refresh rate: 60.000
|
|
+│ │ ├──Preferred scale: 1.0
|
|
+│ │ ├──Supported scales: [1.0, 1.25, 1.5, 1.7475727796554565]
|
|
+│ │ └──Properties: (0)
|
|
+│ └──Properties: (3)
|
|
+│ ├──is-builtin ⇒ no
|
|
+│ ├──display-name ⇒ MetaProduct's Inc. 14"
|
|
+│ └──is-for-lease ⇒ no
|
|
+└──Monitor DP-2 (MetaProduct's Inc. 13")
|
|
+ ├──Vendor: MetaProduct's Inc.
|
|
+ ├──Product: MetaMonitor
|
|
+ ├──Serial: 0x1234561
|
|
+ ├──Modes (4)
|
|
+ │ ├──2560x1440@60.000
|
|
+ │ │ ├──Dimension: 2560x1440
|
|
+ │ │ ├──Refresh rate: 60.000
|
|
+ │ │ ├──Preferred scale: 1.7582417726516724
|
|
+ │ │ ├──Supported scales: [1.0, 1.25, 1.495327115058899, 1.7582417726516724, 2.0, 2.253521203994751, 2.5, 2.7586207389831543, 3.0188679695129395]
|
|
+ │ │ └──Properties: (2)
|
|
+ │ │ ├──is-current ⇒ yes
|
|
+ │ │ └──is-preferred ⇒ yes
|
|
+ │ ├──1440x900@60.000
|
|
+ │ │ ├──Dimension: 1440x900
|
|
+ │ │ ├──Refresh rate: 60.000
|
|
+ │ │ ├──Preferred scale: 1.0
|
|
+ │ │ ├──Supported scales: [1.0, 1.25, 1.5, 1.7475727796554565]
|
|
+ │ │ └──Properties: (0)
|
|
+ │ ├──1366x768@60.000
|
|
+ │ │ ├──Dimension: 1366x768
|
|
+ │ │ ├──Refresh rate: 60.000
|
|
+ │ │ ├──Preferred scale: 1.0
|
|
+ │ │ ├──Supported scales: [1.0]
|
|
+ │ │ └──Properties: (0)
|
|
+ │ └──800x600@60.000
|
|
+ │ ├──Dimension: 800x600
|
|
+ │ ├──Refresh rate: 60.000
|
|
+ │ ├──Preferred scale: 1.0
|
|
+ │ ├──Supported scales: [1.0]
|
|
+ │ └──Properties: (0)
|
|
+ └──Properties: (3)
|
|
+ ├──is-builtin ⇒ no
|
|
+ ├──display-name ⇒ MetaProduct's Inc. 13"
|
|
+ └──is-for-lease ⇒ no
|
|
+
|
|
+Logical monitors:
|
|
+├──Logical monitor #1
|
|
+│ ├──Position: (0, 0)
|
|
+│ ├──Scale: 2.2018349170684814
|
|
+│ ├──Transform: normal
|
|
+│ ├──Primary: yes
|
|
+│ ├──Monitors: (1)
|
|
+│ │ └──DP-1 (MetaProduct's Inc. 14")
|
|
+│ └──Properties: (0)
|
|
+└──Logical monitor #2
|
|
+ ├──Position: (1744, 0)
|
|
+ ├──Scale: 1.7582417726516724
|
|
+ ├──Transform: normal
|
|
+ ├──Primary: no
|
|
+ ├──Monitors: (1)
|
|
+ │ └──DP-2 (MetaProduct's Inc. 13")
|
|
+ └──Properties: (0)
|
|
+
|
|
+Properties: (2)
|
|
+├──layout-mode ⇒ logical
|
|
+└──supports-changing-layout-mode ⇒ yes
|
|
diff --git a/src/tests/generic.test.in b/src/tests/generic.test.in
|
|
index 623e51f24d..d9b8ed3c8a 100644
|
|
--- a/src/tests/generic.test.in
|
|
+++ b/src/tests/generic.test.in
|
|
@@ -1,5 +1,5 @@
|
|
[Test]
|
|
Description=Mutter test: @testname@
|
|
-Exec=sh -ec 'env G_TEST_SRCDIR=@pkgdatadir@ G_TEST_BUILDDIR=@libexecdir@/installed-tests/mutter-@apiversion@ MUTTER_REF_TEST_RESULT_DIR=@reftestresultdir@ @libexecdir@/installed-tests/mutter-@apiversion@/mutter-installed-dbus-session.py @runnerargs@ -- @libexecdir@/installed-tests/mutter-@apiversion@/@testexecutable@ @testargs@'
|
|
+Exec=sh -ec 'env G_TEST_SRCDIR=@pkgdatadir@ G_TEST_BUILDDIR=@libexecdir@/installed-tests/mutter-@apiversion@ MUTTER_REF_TEST_RESULT_DIR=@reftestresultdir@ MUTTER_GDCTL_TEST_RESULT_DIR=@gdctltestresultdir@ @libexecdir@/installed-tests/mutter-@apiversion@/mutter-installed-dbus-session.py @runnerargs@ -- @libexecdir@/installed-tests/mutter-@apiversion@/@testexecutable@ @testargs@'
|
|
Type=session
|
|
Output=TAP
|
|
diff --git a/src/tests/meson.build b/src/tests/meson.build
|
|
index 9f1fc1157b..a91dfb9893 100644
|
|
--- a/src/tests/meson.build
|
|
+++ b/src/tests/meson.build
|
|
@@ -149,6 +149,7 @@ test_env_variables = {
|
|
'XDG_CONFIG_HOME': mutter_builddir / '.config',
|
|
'MUTTER_TEST_PLUGIN_PATH': '@0@'.format(default_plugin.full_path()),
|
|
'MUTTER_REF_TEST_RESULT_DIR': mutter_builddir / 'meson-logs' / 'tests' / 'ref-tests',
|
|
+ 'MUTTER_GDCTL_TEST_RESULT_DIR': mutter_builddir / 'meson-logs' / 'tests' / 'gdctl',
|
|
'GSETTINGS_SCHEMA_DIR': ':'.join([mutter_builddir / 'src' / 'tests',
|
|
locally_compiled_schemas_dir]),
|
|
}
|
|
@@ -208,6 +209,9 @@ install_data(
|
|
install_subdir('dbusmock-templates',
|
|
install_dir: tests_datadir,
|
|
)
|
|
+install_subdir('gdctl',
|
|
+ install_dir: tests_datadir,
|
|
+)
|
|
|
|
if have_installed_tests
|
|
configure_file(
|
|
@@ -279,6 +283,12 @@ test_cases += [
|
|
'suite': 'backend',
|
|
'sources': [ 'monitor-backlight-tests.c', ]
|
|
},
|
|
+ {
|
|
+ 'name': 'monitor-dbus',
|
|
+ 'suite': 'backend',
|
|
+ 'sources': [ 'monitor-dbus-tests.c', ],
|
|
+ 'args': gdctl.full_path(),
|
|
+ },
|
|
{
|
|
'name': 'stage-views',
|
|
'suite': 'compositor',
|
|
@@ -794,6 +804,7 @@ foreach test_case: test_cases
|
|
|
|
args = test_case.get('args', [])
|
|
|
|
+ message('test @0@ args: @1@'.format(test_case['name'], args))
|
|
test(test_case['name'], test_executable,
|
|
suite: ['core', 'mutter/' + test_case['suite']],
|
|
args: args,
|
|
@@ -1044,6 +1055,7 @@ if have_installed_tests
|
|
installed_tests_cdata.set('libexecdir', libexecdir)
|
|
installed_tests_cdata.set('pkgdatadir', pkgdatadir)
|
|
installed_tests_cdata.set('reftestresultdir', '/tmp/mutter-ref-test-results')
|
|
+ installed_tests_cdata.set('gdctltestresultdir', '/tmp/mutter-gdctl-test-results')
|
|
installed_tests_cdata.set('testname', test_case['name'])
|
|
installed_tests_cdata.set('testexecutable', 'mutter-' + test_case['name'])
|
|
installed_tests_cdata.set('testargs', ' '.join(test_case.get('args', [])))
|
|
diff --git a/src/tests/monitor-dbus-tests.c b/src/tests/monitor-dbus-tests.c
|
|
new file mode 100644
|
|
index 0000000000..f8f56e1cd4
|
|
--- /dev/null
|
|
+++ b/src/tests/monitor-dbus-tests.c
|
|
@@ -0,0 +1,298 @@
|
|
+/*
|
|
+ * Copyright (C) 2024 Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include "config.h"
|
|
+
|
|
+#include <stdarg.h>
|
|
+
|
|
+#include "tests/meta-monitor-test-utils.h"
|
|
+#include "tests/meta-test/meta-context-test.h"
|
|
+
|
|
+static MetaContext *test_context;
|
|
+static char *gdctl_path;
|
|
+
|
|
+static MonitorTestCaseSetup test_case_setup = {
|
|
+ .modes = {
|
|
+ {
|
|
+ .width = 3840,
|
|
+ .height = 2160,
|
|
+ .refresh_rate = 60.0
|
|
+ },
|
|
+ {
|
|
+ .width = 3840,
|
|
+ .height = 2160,
|
|
+ .refresh_rate = 30.0
|
|
+ },
|
|
+ {
|
|
+ .width = 2560,
|
|
+ .height = 1440,
|
|
+ .refresh_rate = 60.0
|
|
+ },
|
|
+ {
|
|
+ .width = 1440,
|
|
+ .height = 900,
|
|
+ .refresh_rate = 60.0
|
|
+ },
|
|
+ {
|
|
+ .width = 1366,
|
|
+ .height = 768,
|
|
+ .refresh_rate = 60.0
|
|
+ },
|
|
+ {
|
|
+ .width = 800,
|
|
+ .height = 600,
|
|
+ .refresh_rate = 60.0
|
|
+ },
|
|
+ },
|
|
+ .n_modes = 6,
|
|
+ .outputs = {
|
|
+ {
|
|
+ .crtc = 0,
|
|
+ .modes = { 0, 1, 2, 3 },
|
|
+ .n_modes = 4,
|
|
+ .preferred_mode = 0,
|
|
+ .possible_crtcs = { 0 },
|
|
+ .n_possible_crtcs = 1,
|
|
+ .width_mm = 300,
|
|
+ .height_mm = 190,
|
|
+ .dynamic_scale = TRUE,
|
|
+ },
|
|
+ {
|
|
+ .crtc = 1,
|
|
+ .modes = { 2, 3, 4, 5 },
|
|
+ .n_modes = 4,
|
|
+ .preferred_mode = 2,
|
|
+ .possible_crtcs = { 1 },
|
|
+ .n_possible_crtcs = 1,
|
|
+ .width_mm = 290,
|
|
+ .height_mm = 180,
|
|
+ .dynamic_scale = TRUE,
|
|
+ },
|
|
+ },
|
|
+ .n_outputs = 2,
|
|
+ .n_crtcs = 2
|
|
+};
|
|
+
|
|
+static void
|
|
+read_all_cb (GObject *source_object,
|
|
+ GAsyncResult *res,
|
|
+ gpointer user_data)
|
|
+{
|
|
+ gboolean *done = user_data;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ g_input_stream_read_all_finish (G_INPUT_STREAM (source_object),
|
|
+ res,
|
|
+ NULL,
|
|
+ &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ *done = TRUE;
|
|
+}
|
|
+
|
|
+static void
|
|
+wait_check_cb (GObject *source_object,
|
|
+ GAsyncResult *res,
|
|
+ gpointer user_data)
|
|
+{
|
|
+ gboolean *done = user_data;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ g_subprocess_wait_check_finish (G_SUBPROCESS (source_object), res, &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ *done = TRUE;
|
|
+}
|
|
+
|
|
+static char *
|
|
+save_output (const char *output,
|
|
+ const char *expected_output_file)
|
|
+{
|
|
+ const char *gdctl_result_dir;
|
|
+ char *output_path;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ gdctl_result_dir = g_getenv ("MUTTER_GDCTL_TEST_RESULT_DIR");
|
|
+ g_assert_no_errno (g_mkdir_with_parents (gdctl_result_dir, 0755));
|
|
+
|
|
+ output_path = g_strdup_printf ("%s/%s",
|
|
+ gdctl_result_dir,
|
|
+ expected_output_file);
|
|
+
|
|
+ g_file_set_contents (output_path, output, -1, &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ return output_path;
|
|
+}
|
|
+
|
|
+static void
|
|
+run_diff (const char *output_path,
|
|
+ const char *expected_output_path)
|
|
+{
|
|
+ g_autoptr (GSubprocessLauncher) launcher = NULL;
|
|
+ g_autoptr (GSubprocess) subprocess = NULL;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
|
|
+ subprocess = g_subprocess_launcher_spawn (launcher,
|
|
+ &error,
|
|
+ "diff",
|
|
+ "-u",
|
|
+ expected_output_path,
|
|
+ output_path,
|
|
+ NULL);
|
|
+ g_subprocess_wait (subprocess, NULL, &error);
|
|
+ g_assert_no_error (error);
|
|
+}
|
|
+
|
|
+static void
|
|
+check_gdctl_output (const char *expected_output_file,
|
|
+ ...)
|
|
+{
|
|
+ g_autoptr (GPtrArray) args = NULL;
|
|
+ va_list va_args;
|
|
+ char *arg;
|
|
+ g_autoptr (GSubprocessLauncher) launcher = NULL;
|
|
+ g_autoptr (GSubprocess) subprocess = NULL;
|
|
+ GInputStream *stdout_pipe;
|
|
+ size_t max_output_size;
|
|
+ g_autofree char *output = NULL;
|
|
+ gboolean read_done = FALSE;
|
|
+ gboolean process_done = FALSE;
|
|
+ GError *error = NULL;
|
|
+ g_autofree char *expected_output_path = NULL;
|
|
+ g_autofree char *expected_output = NULL;
|
|
+
|
|
+ args = g_ptr_array_new ();
|
|
+ g_ptr_array_add (args, gdctl_path);
|
|
+ va_start (va_args, expected_output_file);
|
|
+ while ((arg = va_arg (va_args, char *)))
|
|
+ g_ptr_array_add (args, arg);
|
|
+ va_end (va_args);
|
|
+ g_ptr_array_add (args, NULL);
|
|
+
|
|
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
|
+ G_SUBPROCESS_FLAGS_STDERR_PIPE);
|
|
+
|
|
+ subprocess = g_subprocess_launcher_spawnv (launcher,
|
|
+ (const char * const*) args->pdata,
|
|
+ &error);
|
|
+ stdout_pipe = g_subprocess_get_stdout_pipe (subprocess);
|
|
+ max_output_size = 1024 * 1024;
|
|
+ output = g_malloc0 (max_output_size);
|
|
+ g_input_stream_read_all_async (stdout_pipe,
|
|
+ output,
|
|
+ max_output_size - 1,
|
|
+ G_PRIORITY_DEFAULT,
|
|
+ NULL,
|
|
+ read_all_cb,
|
|
+ &read_done);
|
|
+
|
|
+ g_subprocess_wait_check_async (subprocess, NULL,
|
|
+ wait_check_cb, &process_done);
|
|
+
|
|
+ while (!read_done || !process_done)
|
|
+ g_main_context_iteration (NULL, TRUE);
|
|
+
|
|
+ expected_output_path = g_test_build_filename (G_TEST_DIST,
|
|
+ "tests",
|
|
+ "gdctl",
|
|
+ expected_output_file,
|
|
+ NULL);
|
|
+ g_file_get_contents (expected_output_path,
|
|
+ &expected_output,
|
|
+ NULL,
|
|
+ &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ if (g_strcmp0 (expected_output, output) != 0)
|
|
+ {
|
|
+ g_autofree char *output_path = NULL;
|
|
+
|
|
+ output_path = save_output (output, expected_output_file);
|
|
+ run_diff (output_path, expected_output_path);
|
|
+ g_error ("Incorrect gdctl output");
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+meta_test_monitor_dbus_get_state (void)
|
|
+{
|
|
+ MetaBackend *backend = meta_context_get_backend (test_context);
|
|
+ MetaMonitorManager *monitor_manager =
|
|
+ meta_backend_get_monitor_manager (backend);
|
|
+ MetaMonitorManagerTest *monitor_manager_test =
|
|
+ META_MONITOR_MANAGER_TEST (monitor_manager);
|
|
+ MetaMonitorTestSetup *test_setup;
|
|
+
|
|
+ test_setup = meta_create_monitor_test_setup (backend,
|
|
+ &test_case_setup,
|
|
+ MONITOR_TEST_FLAG_NO_STORED);
|
|
+ meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
|
|
+
|
|
+ check_gdctl_output ("show",
|
|
+ "show", NULL);
|
|
+ check_gdctl_output ("show-properties",
|
|
+ "show", "--properties", NULL);
|
|
+ check_gdctl_output ("show-modes",
|
|
+ "show", "--modes", NULL);
|
|
+ check_gdctl_output ("show-verbose",
|
|
+ "show", "--verbose", NULL);
|
|
+}
|
|
+
|
|
+static void
|
|
+init_tests (void)
|
|
+{
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/get-state",
|
|
+ meta_test_monitor_dbus_get_state);
|
|
+}
|
|
+
|
|
+int
|
|
+main (int argc,
|
|
+ char **argv)
|
|
+{
|
|
+ g_autoptr (MetaContext) context = NULL;
|
|
+ g_autoptr (GFile) gdctl_file = NULL;
|
|
+ char **argv_ignored = NULL;
|
|
+ GOptionEntry options[] = {
|
|
+ {
|
|
+ G_OPTION_REMAINING,
|
|
+ .arg = G_OPTION_ARG_STRING_ARRAY,
|
|
+ &argv_ignored,
|
|
+ .arg_description = "GDCTL-PATH"
|
|
+ },
|
|
+ { NULL }
|
|
+ };
|
|
+
|
|
+ context = meta_create_test_context (META_CONTEXT_TEST_TYPE_TEST,
|
|
+ META_CONTEXT_TEST_FLAG_NO_X11);
|
|
+ meta_context_add_option_entries (context, options, NULL);
|
|
+ g_assert_true (meta_context_configure (context, &argc, &argv, NULL));
|
|
+
|
|
+ g_assert_nonnull (argv_ignored);
|
|
+ g_assert_nonnull (argv_ignored[0]);
|
|
+ g_assert_null (argv_ignored[1]);
|
|
+ gdctl_path = argv_ignored[0];
|
|
+
|
|
+ test_context = context;
|
|
+
|
|
+ init_tests ();
|
|
+
|
|
+ return meta_context_test_run_tests (META_CONTEXT_TEST (context),
|
|
+ META_TEST_RUN_FLAG_NONE);
|
|
+}
|
|
diff --git a/tools/meson.build b/tools/meson.build
|
|
index 2603ed3d87..763507c2e9 100644
|
|
--- a/tools/meson.build
|
|
+++ b/tools/meson.build
|
|
@@ -3,4 +3,5 @@ install_data(
|
|
install_dir: bindir,
|
|
)
|
|
|
|
+gdctl = find_program('gdctl')
|
|
get_state_tool = find_program('get-state.py')
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From ca85c10b64c43a8c261b70244ec1d017ba22cd2e Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 16:49:08 +0100
|
|
Subject: [PATCH 15/28] tests/monitor-test-utils: Add debug log for logical
|
|
monitor checking
|
|
|
|
This was helpful when figuring out what logical monitor test data was
|
|
wrong.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit fe79188b58cec406585c42d7bb6197c92bb05f4a)
|
|
---
|
|
src/tests/meta-monitor-test-utils.c | 6 ++++++
|
|
1 file changed, 6 insertions(+)
|
|
|
|
diff --git a/src/tests/meta-monitor-test-utils.c b/src/tests/meta-monitor-test-utils.c
|
|
index c022afdea8..4a7a46a527 100644
|
|
--- a/src/tests/meta-monitor-test-utils.c
|
|
+++ b/src/tests/meta-monitor-test-utils.c
|
|
@@ -238,6 +238,12 @@ check_logical_monitor (MetaMonitorManager *monitor_manager,
|
|
GList *l;
|
|
int i;
|
|
|
|
+ g_debug ("Checking logical monitor with layout %dx%d+%d+%d",
|
|
+ test_logical_monitor->layout.width,
|
|
+ test_logical_monitor->layout.height,
|
|
+ test_logical_monitor->layout.x,
|
|
+ test_logical_monitor->layout.y);
|
|
+
|
|
logical_monitor = logical_monitor_from_layout (monitor_manager,
|
|
&test_logical_monitor->layout);
|
|
g_assert_nonnull (logical_monitor);
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 07b3e24e2b8f8c5b75f4dffe083107b2eb119e56 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 16:52:41 +0100
|
|
Subject: [PATCH 16/28] tests/monitor-dbus: Test changing configuration
|
|
|
|
Uses some various combinations of the gdctl commands that configures the
|
|
available monitors differently.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 839f67f1de1d3f84bebe04b12f4ef0e91d0ccc54)
|
|
---
|
|
src/tests/monitor-dbus-tests.c | 412 +++++++++++++++++++++++++++++++++
|
|
1 file changed, 412 insertions(+)
|
|
|
|
diff --git a/src/tests/monitor-dbus-tests.c b/src/tests/monitor-dbus-tests.c
|
|
index f8f56e1cd4..82e3178c6a 100644
|
|
--- a/src/tests/monitor-dbus-tests.c
|
|
+++ b/src/tests/monitor-dbus-tests.c
|
|
@@ -20,6 +20,7 @@
|
|
|
|
#include <stdarg.h>
|
|
|
|
+#include "backends/meta-monitor-config-manager.h"
|
|
#include "tests/meta-monitor-test-utils.h"
|
|
#include "tests/meta-test/meta-context-test.h"
|
|
|
|
@@ -88,6 +89,149 @@ static MonitorTestCaseSetup test_case_setup = {
|
|
.n_crtcs = 2
|
|
};
|
|
|
|
+static MonitorTestCaseExpect test_case_expect = {
|
|
+ .monitors = {
|
|
+ {
|
|
+ .outputs = { 0 },
|
|
+ .n_outputs = 1,
|
|
+ .modes = {
|
|
+ {
|
|
+ .width = 3840,
|
|
+ .height = 2160,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 0,
|
|
+ .crtc_mode = 0,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 3840,
|
|
+ .height = 2160,
|
|
+ .refresh_rate = 30.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 0,
|
|
+ .crtc_mode = 1,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 2560,
|
|
+ .height = 1440,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 0,
|
|
+ .crtc_mode = 2,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 1440,
|
|
+ .height = 900,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 0,
|
|
+ .crtc_mode = 3,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ .n_modes = 4,
|
|
+ .current_mode = 0,
|
|
+ .width_mm = 300,
|
|
+ .height_mm = 190,
|
|
+ },
|
|
+ {
|
|
+ .outputs = { 1 },
|
|
+ .n_outputs = 1,
|
|
+ .modes = {
|
|
+ {
|
|
+ .width = 2560,
|
|
+ .height = 1440,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 1,
|
|
+ .crtc_mode = 2,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 1440,
|
|
+ .height = 900,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 1,
|
|
+ .crtc_mode = 3,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 1366,
|
|
+ .height = 768,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 1,
|
|
+ .crtc_mode = 4,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .width = 800,
|
|
+ .height = 600,
|
|
+ .refresh_rate = 60.0,
|
|
+ .crtc_modes = {
|
|
+ {
|
|
+ .output = 1,
|
|
+ .crtc_mode = 5,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ .n_modes = 4,
|
|
+ .current_mode = 0,
|
|
+ .width_mm = 290,
|
|
+ .height_mm = 180,
|
|
+ },
|
|
+ },
|
|
+ .n_monitors = 2,
|
|
+ .logical_monitors = {
|
|
+ {
|
|
+ .monitors = { 0 },
|
|
+ .n_monitors = 1,
|
|
+ .layout = { .x = 0, .y = 0, .width = 1744, .height = 981 },
|
|
+ .scale = 2.2018349170684814,
|
|
+ },
|
|
+ {
|
|
+ .monitors = { 1 },
|
|
+ .n_monitors = 1,
|
|
+ .layout = { .x = 1744, .y = 0, .width = 1456, .height = 819 },
|
|
+ .scale = 1.7582417726516724,
|
|
+ },
|
|
+ },
|
|
+ .n_logical_monitors = 2,
|
|
+ .primary_logical_monitor = 0,
|
|
+ .n_outputs = 2,
|
|
+ .crtcs = {
|
|
+ {
|
|
+ .current_mode = 0,
|
|
+ },
|
|
+ {
|
|
+ .current_mode = 2,
|
|
+ .x = 1744,
|
|
+ }
|
|
+ },
|
|
+ .n_crtcs = 2,
|
|
+ .screen_width = 3200,
|
|
+ .screen_height = 981,
|
|
+};
|
|
+
|
|
static void
|
|
read_all_cb (GObject *source_object,
|
|
GAsyncResult *res,
|
|
@@ -160,6 +304,39 @@ run_diff (const char *output_path,
|
|
g_assert_no_error (error);
|
|
}
|
|
|
|
+static void
|
|
+check_gdctl_result (const char *first_argument,
|
|
+ ...)
|
|
+{
|
|
+ g_autoptr (GPtrArray) args = NULL;
|
|
+ va_list va_args;
|
|
+ char *arg;
|
|
+ g_autoptr (GSubprocessLauncher) launcher = NULL;
|
|
+ g_autoptr (GSubprocess) subprocess = NULL;
|
|
+ gboolean process_done = FALSE;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ args = g_ptr_array_new ();
|
|
+ g_ptr_array_add (args, gdctl_path);
|
|
+ g_ptr_array_add (args, (char *) first_argument);
|
|
+ va_start (va_args, first_argument);
|
|
+ while ((arg = va_arg (va_args, char *)))
|
|
+ g_ptr_array_add (args, arg);
|
|
+ va_end (va_args);
|
|
+ g_ptr_array_add (args, NULL);
|
|
+
|
|
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
|
|
+
|
|
+ subprocess = g_subprocess_launcher_spawnv (launcher,
|
|
+ (const char * const*) args->pdata,
|
|
+ &error);
|
|
+ g_subprocess_wait_check_async (subprocess, NULL,
|
|
+ wait_check_cb, &process_done);
|
|
+
|
|
+ while (!process_done)
|
|
+ g_main_context_iteration (NULL, TRUE);
|
|
+}
|
|
+
|
|
static void
|
|
check_gdctl_output (const char *expected_output_file,
|
|
...)
|
|
@@ -255,11 +432,246 @@ meta_test_monitor_dbus_get_state (void)
|
|
"show", "--verbose", NULL);
|
|
}
|
|
|
|
+static void
|
|
+meta_test_monitor_dbus_apply_verify (void)
|
|
+{
|
|
+ MetaBackend *backend = meta_context_get_backend (test_context);
|
|
+ MetaMonitorManager *monitor_manager =
|
|
+ meta_backend_get_monitor_manager (backend);
|
|
+ MetaMonitorConfigManager *config_manager =
|
|
+ meta_monitor_manager_get_config_manager (monitor_manager);
|
|
+ MetaMonitorManagerTest *monitor_manager_test =
|
|
+ META_MONITOR_MANAGER_TEST (monitor_manager);
|
|
+ MetaMonitorTestSetup *test_setup;
|
|
+ MetaMonitorsConfig *config;
|
|
+
|
|
+ test_setup = meta_create_monitor_test_setup (backend,
|
|
+ &test_case_setup,
|
|
+ MONITOR_TEST_FLAG_NO_STORED);
|
|
+ meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
|
|
+
|
|
+ config = meta_monitor_config_manager_get_current (config_manager);
|
|
+
|
|
+ check_gdctl_result ("set",
|
|
+ "--verbose",
|
|
+ "--verify",
|
|
+ "--layout-mode", "logical",
|
|
+ "--logical-monitor",
|
|
+ "--primary",
|
|
+ "--monitor", "DP-1",
|
|
+ "--logical-monitor",
|
|
+ "--monitor", "DP-2",
|
|
+ "--right-of", "DP-1",
|
|
+ NULL);
|
|
+ g_assert_true (config ==
|
|
+ meta_monitor_config_manager_get_current (config_manager));
|
|
+}
|
|
+
|
|
+static void
|
|
+setup_apply_configuration_test (void)
|
|
+{
|
|
+ MetaBackend *backend = meta_context_get_backend (test_context);
|
|
+ MetaMonitorManager *monitor_manager =
|
|
+ meta_backend_get_monitor_manager (backend);
|
|
+ MetaMonitorManagerTest *monitor_manager_test =
|
|
+ META_MONITOR_MANAGER_TEST (monitor_manager);
|
|
+ MetaMonitorTestSetup *test_setup;
|
|
+
|
|
+ test_setup = meta_create_monitor_test_setup (backend,
|
|
+ &test_case_setup,
|
|
+ MONITOR_TEST_FLAG_NO_STORED);
|
|
+ meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
|
|
+
|
|
+ META_TEST_LOG_CALL ("Checking monitor configuration",
|
|
+ meta_check_monitor_configuration (test_context,
|
|
+ &test_case_expect));
|
|
+}
|
|
+
|
|
+static void
|
|
+meta_test_monitor_dbus_apply_left_of (void)
|
|
+{
|
|
+ MonitorTestCaseExpect expect;
|
|
+
|
|
+ setup_apply_configuration_test ();
|
|
+
|
|
+ check_gdctl_result ("set",
|
|
+ "--verbose",
|
|
+ "--layout-mode", "logical",
|
|
+ "--logical-monitor",
|
|
+ "--primary",
|
|
+ "--monitor", "DP-1",
|
|
+ "--logical-monitor",
|
|
+ "--monitor", "DP-2",
|
|
+ "--left-of", "DP-1",
|
|
+ NULL);
|
|
+
|
|
+ expect = test_case_expect;
|
|
+ expect.logical_monitors[0].layout.x = 1456;
|
|
+ expect.logical_monitors[1].layout.x = 0;
|
|
+ expect.crtcs[0].x = 1456;
|
|
+ expect.crtcs[1].x = 0;
|
|
+ META_TEST_LOG_CALL ("Checking monitor configuration",
|
|
+ meta_check_monitor_configuration (test_context,
|
|
+ &expect));
|
|
+}
|
|
+
|
|
+static void
|
|
+meta_test_monitor_dbus_apply_right_of_transform (void)
|
|
+{
|
|
+ MonitorTestCaseExpect expect;
|
|
+
|
|
+ setup_apply_configuration_test ();
|
|
+
|
|
+ check_gdctl_result ("set",
|
|
+ "--verbose",
|
|
+ "--layout-mode", "logical",
|
|
+ "--logical-monitor",
|
|
+ "--primary",
|
|
+ "--monitor", "DP-2",
|
|
+ "--transform", "270",
|
|
+ "--logical-monitor",
|
|
+ "--monitor", "DP-1",
|
|
+ "--right-of", "DP-2",
|
|
+ "--y", "400",
|
|
+ NULL);
|
|
+
|
|
+ expect = test_case_expect;
|
|
+ expect.logical_monitors[0].layout.x = 0;
|
|
+ expect.logical_monitors[0].layout.y = 0;
|
|
+ expect.logical_monitors[0].layout.width = 819;
|
|
+ expect.logical_monitors[0].layout.height = 1456;
|
|
+ expect.logical_monitors[0].scale = 1.7582417726516724;
|
|
+ expect.logical_monitors[0].transform = MTK_MONITOR_TRANSFORM_270;
|
|
+ expect.logical_monitors[0].monitors[0] = 1;
|
|
+
|
|
+ expect.logical_monitors[1].layout.x = 819;
|
|
+ expect.logical_monitors[1].layout.y = 400;
|
|
+ expect.logical_monitors[1].layout.width = 1744;
|
|
+ expect.logical_monitors[1].layout.height = 981;
|
|
+ expect.logical_monitors[1].scale = 2.2018349170684814;
|
|
+ expect.logical_monitors[1].monitors[0] = 0;
|
|
+
|
|
+ expect.crtcs[1].x = 0;
|
|
+ expect.crtcs[1].y = 0;
|
|
+ expect.crtcs[1].transform = MTK_MONITOR_TRANSFORM_270;
|
|
+ expect.crtcs[0].x = 819;
|
|
+ expect.crtcs[0].y = 400;
|
|
+ expect.screen_width = 2563;
|
|
+ expect.screen_height = 1456;
|
|
+ META_TEST_LOG_CALL ("Checking monitor configuration",
|
|
+ meta_check_monitor_configuration (test_context,
|
|
+ &expect));
|
|
+}
|
|
+
|
|
+static void
|
|
+meta_test_monitor_dbus_apply_mode_scale_below_transform (void)
|
|
+{
|
|
+ MonitorTestCaseExpect expect;
|
|
+
|
|
+ setup_apply_configuration_test ();
|
|
+
|
|
+ check_gdctl_result ("set",
|
|
+ "--verbose",
|
|
+ "--layout-mode", "logical",
|
|
+ "--logical-monitor",
|
|
+ "--primary",
|
|
+ "--monitor", "DP-2",
|
|
+ "--transform", "270",
|
|
+ "--logical-monitor",
|
|
+ "--monitor", "DP-1",
|
|
+ "--below", "DP-2",
|
|
+ "--transform", "90",
|
|
+ "--x", "100",
|
|
+ "--mode", "1440x900@60.000",
|
|
+ "--scale", "1.5",
|
|
+ NULL);
|
|
+
|
|
+ expect = test_case_expect;
|
|
+ expect.monitors[0].current_mode = 3;
|
|
+ expect.logical_monitors[0].layout.x = 0;
|
|
+ expect.logical_monitors[0].layout.y = 0;
|
|
+ expect.logical_monitors[0].layout.width = 819;
|
|
+ expect.logical_monitors[0].layout.height = 1456;
|
|
+ expect.logical_monitors[0].scale = 1.7582417726516724;
|
|
+ expect.logical_monitors[0].transform = MTK_MONITOR_TRANSFORM_270;
|
|
+ expect.logical_monitors[0].monitors[0] = 1;
|
|
+ expect.logical_monitors[1].layout.x = 100;
|
|
+ expect.logical_monitors[1].layout.y = 1456;
|
|
+ expect.logical_monitors[1].layout.width = 600;
|
|
+ expect.logical_monitors[1].layout.height = 960;
|
|
+ expect.logical_monitors[1].scale = 1.5;
|
|
+ expect.logical_monitors[1].transform = MTK_MONITOR_TRANSFORM_90;
|
|
+ expect.logical_monitors[1].monitors[0] = 0;
|
|
+ expect.crtcs[0].x = 100;
|
|
+ expect.crtcs[0].y = 1456;
|
|
+ expect.crtcs[0].current_mode = 3;
|
|
+ expect.crtcs[0].transform = MTK_MONITOR_TRANSFORM_90;
|
|
+ expect.crtcs[1].x = 0;
|
|
+ expect.crtcs[1].y = 0;
|
|
+ expect.crtcs[1].transform = MTK_MONITOR_TRANSFORM_270;
|
|
+ expect.screen_width = 819;
|
|
+ expect.screen_height = 2416;
|
|
+
|
|
+ META_TEST_LOG_CALL ("Checking monitor configuration",
|
|
+ meta_check_monitor_configuration (test_context,
|
|
+ &expect));
|
|
+}
|
|
+
|
|
+static void
|
|
+meta_test_monitor_dbus_apply_mirror (void)
|
|
+{
|
|
+ MonitorTestCaseExpect expect;
|
|
+
|
|
+ setup_apply_configuration_test ();
|
|
+
|
|
+ check_gdctl_result ("set",
|
|
+ "--verbose",
|
|
+ "--layout-mode", "logical",
|
|
+ "--logical-monitor",
|
|
+ "--primary",
|
|
+ "--monitor", "DP-1",
|
|
+ "--mode", "2560x1440@60.000",
|
|
+ "--monitor", "DP-2",
|
|
+ "--scale", "1.7582417726516724",
|
|
+ NULL);
|
|
+
|
|
+ expect = test_case_expect;
|
|
+ expect.monitors[0].current_mode = 2;
|
|
+ expect.logical_monitors[0].layout.width = 1456;
|
|
+ expect.logical_monitors[0].layout.height = 819;
|
|
+ expect.logical_monitors[0].scale = 1.7582417726516724;
|
|
+ expect.logical_monitors[0].monitors[0] = 0;
|
|
+ expect.logical_monitors[0].monitors[1] = 1;
|
|
+ expect.logical_monitors[0].n_monitors = 2;
|
|
+ expect.n_logical_monitors = 1;
|
|
+ expect.screen_width = 1456;
|
|
+ expect.screen_height = 819;
|
|
+ expect.crtcs[0].x = 0;
|
|
+ expect.crtcs[0].y = 0;
|
|
+ expect.crtcs[0].current_mode = 2;
|
|
+ expect.crtcs[1].x = 0;
|
|
+ expect.crtcs[1].y = 0;
|
|
+
|
|
+ META_TEST_LOG_CALL ("Checking monitor configuration",
|
|
+ meta_check_monitor_configuration (test_context,
|
|
+ &expect));
|
|
+}
|
|
+
|
|
static void
|
|
init_tests (void)
|
|
{
|
|
g_test_add_func ("/backends/native/monitor/dbus/get-state",
|
|
meta_test_monitor_dbus_get_state);
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/apply/verify",
|
|
+ meta_test_monitor_dbus_apply_verify);
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/apply/left-of",
|
|
+ meta_test_monitor_dbus_apply_left_of);
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/apply/right-of-transform",
|
|
+ meta_test_monitor_dbus_apply_right_of_transform);
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/apply/mode-scale-below-transform",
|
|
+ meta_test_monitor_dbus_apply_mode_scale_below_transform);
|
|
+ g_test_add_func ("/backends/native/monitor/dbus/apply/mirror",
|
|
+ meta_test_monitor_dbus_apply_mirror);
|
|
}
|
|
|
|
int
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 16eca2a30b83376197cdb8e8f0baf57f8306f2ee Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 18 Dec 2024 22:53:09 +0100
|
|
Subject: [PATCH 17/28] Add gdctl man page using rst2man
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 3cc5d201a249ffb60b5b84b39e7ea0a536feec39)
|
|
---
|
|
doc/man/gdctl.rst | 164 ++++++++++++++++++++++++++++++++++++++++++++
|
|
doc/man/meson.build | 11 +++
|
|
meson.build | 2 +
|
|
3 files changed, 177 insertions(+)
|
|
create mode 100644 doc/man/gdctl.rst
|
|
|
|
diff --git a/doc/man/gdctl.rst b/doc/man/gdctl.rst
|
|
new file mode 100644
|
|
index 0000000000..d62013fe12
|
|
--- /dev/null
|
|
+++ b/doc/man/gdctl.rst
|
|
@@ -0,0 +1,164 @@
|
|
+=====
|
|
+gdctl
|
|
+=====
|
|
+
|
|
+------------------------
|
|
+GNOME Display Controller
|
|
+------------------------
|
|
+
|
|
+:Manual section: 1
|
|
+:Manual group: User Commands
|
|
+
|
|
+SYNOPSIS
|
|
+--------
|
|
+**gdctl** [-h] COMMAND ...
|
|
+
|
|
+DESCRIPTION
|
|
+-----------
|
|
+gdctl provides means to show the active monitor configuration, and set new
|
|
+monitor configuration using command line arguments.
|
|
+
|
|
+It requires a compositor that supports the ``org.gnome.Mutter.DisplayConfig``
|
|
+D-Bus API, such as GNOME Shell.
|
|
+
|
|
+COMMANDS
|
|
+--------
|
|
+``show``
|
|
+
|
|
+ Show the current display configuration
|
|
+
|
|
+``set``
|
|
+
|
|
+ Set a new display configuration
|
|
+
|
|
+SHOW OPTIONS
|
|
+------------
|
|
+``--help``, ``-h``
|
|
+
|
|
+ Show a help message and exit.
|
|
+
|
|
+``--modes``, ``-m``
|
|
+
|
|
+ List available monitor modes.
|
|
+
|
|
+``--properties``, ``-p``
|
|
+
|
|
+ List properties.
|
|
+
|
|
+``--verbose``, ``-v``
|
|
+
|
|
+ Display all available information (equivalent to ``--modes --properties``).
|
|
+
|
|
+SET OPTIONS
|
|
+-----------
|
|
+
|
|
+``--help``, ``-h``
|
|
+
|
|
+ Show a help message and exit.
|
|
+
|
|
+``--persistent``, ``-P``
|
|
+
|
|
+ Store applied configuration on persistant storage and restore when applicable.
|
|
+
|
|
+``--verbose``, ``-v``
|
|
+
|
|
+ Print configuration to standard out before applying it.
|
|
+
|
|
+``--verify``, ``-V``
|
|
+
|
|
+ Only verify, without applying, the configuration.
|
|
+
|
|
+``--layout-mode LAYOUT-MODE``, ``-l``
|
|
+
|
|
+ Specify the layout mode the configuration should use. Either ``logical``, or
|
|
+ ``physical``.
|
|
+
|
|
+``--logical-monitor``, ``-L``
|
|
+
|
|
+ Add and configure a logical monitor. See LOGICAL MONITOR OPTIONS.
|
|
+
|
|
+LOGICAL MONITOR OPTIONS
|
|
+-----------------------
|
|
+
|
|
+``--monitor CONNECTOR``, ``-M CONNECTOR``
|
|
+
|
|
+ Add a monitor to the currently configured logical monitor. All monitors
|
|
+ within the same logical monitor must have the same monitor resolution.
|
|
+
|
|
+``--primary``, ``-p``
|
|
+
|
|
+ Mark currently configured logical monitor as primary.
|
|
+
|
|
+``--scale SCALE``, ``-s SCALE``
|
|
+
|
|
+ Scale monitors within the currently configured logical monitor with
|
|
+ ``SCALE``. Must be a scale supported by all monitors and their configured
|
|
+ modes.
|
|
+
|
|
+``--transform TRANSFORM``, ``-t TRANSFORM``
|
|
+
|
|
+ Transform monitors within the currently configured logical monitor using
|
|
+ ``TRANSFORM``. Possible transforms are ``normal``, ``90``, ``180``, ``270``,
|
|
+ ``flipped``, ``flipped-90``, ``flipped-270`` and ``flipped-180``.
|
|
+
|
|
+``--x X``, ``-x X``
|
|
+
|
|
+ Set the X position of the currently configured logical monitor.
|
|
+
|
|
+``--y``, ``-y Y`` Y position
|
|
+
|
|
+ Set the Y position of the currently configured logical monitor.
|
|
+
|
|
+``--right-of CONNECTOR``
|
|
+
|
|
+ Place the logical monitor to the right of the logical monitor ``CONNECTOR``
|
|
+ belongs to.
|
|
+
|
|
+``--left-of CONNECTOR`` Place left of other monitor
|
|
+
|
|
+ Place the logical monitor to the left of the logical monitor ``CONNECTOR``
|
|
+ belongs to.
|
|
+
|
|
+``--above CONNECTOR``
|
|
+
|
|
+ Place the logical monitor above the logical monitor ``CONNECTOR`` belongs to.
|
|
+
|
|
+``--below CONNECTOR``
|
|
+
|
|
+ Place the logical monitor below the logical monitor ``CONNECTOR`` belongs to.
|
|
+
|
|
+MONITOR OPTIONS
|
|
+---------------
|
|
+
|
|
+``--mode``, ``-M``
|
|
+
|
|
+ Set the mode of the monitor.
|
|
+
|
|
+EXAMPLES
|
|
+--------
|
|
+
|
|
+Mirror DP-1 and eDP-1, and place DP-2, transformed by 270 degrees, to the right
|
|
+of the two mirrored monitors.
|
|
+
|
|
+::
|
|
+
|
|
+ gdctl set --logical-monitor
|
|
+ --primary
|
|
+ --monitor DP-1
|
|
+ --monitor eDP-1
|
|
+ --logical-monitor
|
|
+ --monitor DP-2
|
|
+ --right-of DP-1
|
|
+
|
|
+BUGS
|
|
+----
|
|
+The bug tracker can be reached by visiting the website
|
|
+https://gitlab.gnome.org/GNOME/mutter/-/issues.
|
|
+Before sending a bug report, please verify that you have the latest version
|
|
+of gnome-shell. Many bugs (major and minor) are fixed at each release, and
|
|
+if yours is out of date, the problem may already have been solved.
|
|
+
|
|
+ADDITIONAL INFORMATION
|
|
+----------------------
|
|
+For further information, visit the website
|
|
+https://gitlab.gnome.org/GNOME/mutter/-/blob/main/README.md.
|
|
diff --git a/doc/man/meson.build b/doc/man/meson.build
|
|
index dd543269da..38649eeb92 100644
|
|
--- a/doc/man/meson.build
|
|
+++ b/doc/man/meson.build
|
|
@@ -1 +1,12 @@
|
|
install_man('mutter.1')
|
|
+
|
|
+rst2man = find_program('rst2man')
|
|
+
|
|
+custom_target('gdctl.1',
|
|
+ input: 'gdctl.rst',
|
|
+ output: 'gdctl.1',
|
|
+ command: [rst2man, '--syntax-highlight=none', '@INPUT@'],
|
|
+ capture: true,
|
|
+ install_dir: mandir + '/man1',
|
|
+ install: true
|
|
+)
|
|
diff --git a/meson.build b/meson.build
|
|
index c630ec2369..be5ec11eb1 100644
|
|
--- a/meson.build
|
|
+++ b/meson.build
|
|
@@ -88,6 +88,7 @@ libdir = prefix / get_option('libdir')
|
|
libexecdir = prefix / get_option('libexecdir')
|
|
includedir = prefix / get_option('includedir')
|
|
sysconfdir = get_option('sysconfdir')
|
|
+mandir = prefix / get_option('mandir')
|
|
|
|
pkgname = '@0@-@1@'.format(meson.project_name(), libmutter_api_version)
|
|
|
|
@@ -713,6 +714,7 @@ meson.add_dist_script('meson/check-version.py', meson.project_version(), 'NEWS')
|
|
summary('prefix', prefix, section: 'Directories')
|
|
summary('libexecdir', libexecdir, section: 'Directories')
|
|
summary('pkgdatadir', pkgdatadir, section: 'Directories')
|
|
+summary('mandir', mandir, section: 'Directories')
|
|
|
|
summary('buildtype', get_option('buildtype'), section: 'Build Configuration')
|
|
summary('debug', get_option('debug'), section: 'Build Configuration')
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From fa91583f581bd8b3699858a29de38a222f1223b4 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Thu, 19 Dec 2024 22:07:46 +0100
|
|
Subject: [PATCH 18/28] ci: Run ruff check/format on gdctl
|
|
|
|
Use a CI job using ruff that enforces the coding style to follow
|
|
`ruff format --line-length 80`, and that no new linting issues appear
|
|
using `ruff check`.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit a0c5c09e9b5b22f73e50a599d62ddabaa5c11022)
|
|
---
|
|
.gitlab-ci.yml | 22 ++++++++++++++++++++--
|
|
1 file changed, 20 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
|
|
index fc88aeffc5..7061ef9126 100644
|
|
--- a/.gitlab-ci.yml
|
|
+++ b/.gitlab-ci.yml
|
|
@@ -105,7 +105,7 @@ variables:
|
|
- .skip-git-clone
|
|
variables:
|
|
FDO_DISTRIBUTION_VERSION: 41
|
|
- BASE_TAG: '2024-09-20.2'
|
|
+ BASE_TAG: '2025-01-29.0'
|
|
MUTTER_USER: 'meta-user'
|
|
FDO_DISTRIBUTION_PACKAGES:
|
|
clang
|
|
@@ -124,6 +124,7 @@ variables:
|
|
zenity
|
|
python3-dbusmock
|
|
gnome-desktop-testing
|
|
+ ruff
|
|
|
|
FDO_DISTRIBUTION_EXEC: |
|
|
set -e
|
|
@@ -343,7 +344,7 @@ build-fedora-container@aarch64:
|
|
- !reference [.pipeline-guard, rules]
|
|
when: manual
|
|
|
|
-check-code-style:
|
|
+check-c-code-style:
|
|
extends:
|
|
- .mutter.distribution-image
|
|
- .mutter.fedora@x86_64
|
|
@@ -363,6 +364,23 @@ check-code-style:
|
|
rules:
|
|
- !reference [.only-merge-requests, rules]
|
|
|
|
+check-python-code-style:
|
|
+ extends:
|
|
+ - .mutter.distribution-image
|
|
+ - .mutter.fedora@x86_64
|
|
+ variables:
|
|
+ PYTHON_FILES:
|
|
+ tools/gdctl
|
|
+ stage: code-review
|
|
+ needs:
|
|
+ - job: build-fedora-container@x86_64
|
|
+ artifacts: false
|
|
+ script:
|
|
+ - ruff format --line-length 80 --check $PYTHON_FILES
|
|
+ - ruff check $PYTHON_FILES
|
|
+ rules:
|
|
+ - !reference [.only-merge-requests, rules]
|
|
+
|
|
.build-mutter-base:
|
|
variables:
|
|
BASE_MESON_OPTIONS:
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 432a33b413322eb0c19d6de77d1e3da87dc791c5 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Fri, 20 Dec 2024 10:15:47 +0100
|
|
Subject: [PATCH 19/28] gdctl: Add bash completion integration
|
|
|
|
This auto-completes things such as available connectors, modes, scales,
|
|
transforms, etc.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit f9bb7aa2e68589d6fd5b10a8d54b211f4dc862e6)
|
|
---
|
|
.gitlab-ci.yml | 4 +-
|
|
meson.build | 3 ++
|
|
meson_options.txt | 6 +++
|
|
tools/gdctl | 95 ++++++++++++++++++++++++++++++++++++++++++-----
|
|
tools/meson.build | 25 +++++++++++++
|
|
5 files changed, 122 insertions(+), 11 deletions(-)
|
|
|
|
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
|
|
index 7061ef9126..5bafb06ee3 100644
|
|
--- a/.gitlab-ci.yml
|
|
+++ b/.gitlab-ci.yml
|
|
@@ -11,6 +11,7 @@ include:
|
|
meson-options:
|
|
-Dxwayland_initfd=enabled
|
|
-Dprofiler=true
|
|
+ -Dbash_completion=false
|
|
|
|
build-sysext:
|
|
before_script:
|
|
@@ -124,6 +125,7 @@ variables:
|
|
zenity
|
|
python3-dbusmock
|
|
gnome-desktop-testing
|
|
+ python3-argcomplete
|
|
ruff
|
|
|
|
FDO_DISTRIBUTION_EXEC: |
|
|
@@ -190,8 +192,6 @@ variables:
|
|
mkdir -p /opt/mutter
|
|
cp build/src/tests/kvm/bzImage /opt/mutter/bzImage
|
|
|
|
- dnf install -y python3-argcomplete
|
|
-
|
|
git clone https://github.com/arighi/virtme-ng.git
|
|
cd virtme-ng
|
|
git fetch --tags
|
|
diff --git a/meson.build b/meson.build
|
|
index be5ec11eb1..6948f478a3 100644
|
|
--- a/meson.build
|
|
+++ b/meson.build
|
|
@@ -358,6 +358,7 @@ have_kvm_tests = false
|
|
have_tty_tests = false
|
|
have_installed_tests = false
|
|
have_x11_tests = false
|
|
+have_bash_completion = get_option('bash_completion')
|
|
|
|
if have_tests
|
|
gtk3_dep = dependency('gtk+-3.0', version: gtk3_req)
|
|
@@ -719,6 +720,8 @@ summary('mandir', mandir, section: 'Directories')
|
|
summary('buildtype', get_option('buildtype'), section: 'Build Configuration')
|
|
summary('debug', get_option('debug'), section: 'Build Configuration')
|
|
|
|
+summary('Bash completion', have_bash_completion, section: 'Shell integration')
|
|
+
|
|
summary('OpenGL', have_gl, section: 'Rendering APIs')
|
|
summary('GLES2', have_gles2, section: 'Rendering APIs')
|
|
summary('EGL', have_egl, section: 'Rendering APIs')
|
|
diff --git a/meson_options.txt b/meson_options.txt
|
|
index ec7acc1372..790df935c9 100644
|
|
--- a/meson_options.txt
|
|
+++ b/meson_options.txt
|
|
@@ -225,3 +225,9 @@ option('libdisplay_info',
|
|
deprecated: {'true': 'enabled', 'false': 'disabled'},
|
|
description: 'Build with or without libdisplay-info'
|
|
)
|
|
+
|
|
+option('bash_completion',
|
|
+ type: 'boolean',
|
|
+ value: true,
|
|
+ description: 'Integrate bash completion for gdctl'
|
|
+)
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 005e0b0d40..caa670eb9c 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -1,11 +1,13 @@
|
|
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
+import argcomplete
|
|
import sys
|
|
|
|
from dataclasses import dataclass
|
|
from gi.repository import GLib, Gio
|
|
from enum import Enum, Flag
|
|
+from argcomplete.completers import BaseCompleter, SuppressCompleter
|
|
|
|
NAME = "org.gnome.Mutter.DisplayConfig"
|
|
INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
@@ -1130,6 +1132,70 @@ class GdctlParser(argparse.ArgumentParser):
|
|
return namespace
|
|
|
|
|
|
+class MonitorCompleter(BaseCompleter):
|
|
+ def __call__(self, **kwargs):
|
|
+ try:
|
|
+ display_config = DisplayConfig()
|
|
+ monitors_state = MonitorsState(display_config)
|
|
+ return tuple(monitors_state.monitors)
|
|
+ except Exception:
|
|
+ return ()
|
|
+
|
|
+
|
|
+class MonitorModeCompleter(BaseCompleter):
|
|
+ def __call__(self, parsed_args=None, **kwargs):
|
|
+ try:
|
|
+ (connector,) = parsed_args._current_sub_group["key"]
|
|
+
|
|
+ display_config = DisplayConfig()
|
|
+ monitors_state = MonitorsState(display_config)
|
|
+
|
|
+ monitor = monitors_state.monitors[connector]
|
|
+ return (mode.name for mode in monitor.modes)
|
|
+ except Exception:
|
|
+ return ()
|
|
+
|
|
+
|
|
+class ScaleCompleter(BaseCompleter):
|
|
+ def __call__(self, parsed_args=None, **kwargs):
|
|
+ try:
|
|
+ (connector,) = parsed_args._current_sub_group["key"]
|
|
+
|
|
+ display_config = DisplayConfig()
|
|
+ monitors_state = MonitorsState(display_config)
|
|
+
|
|
+ monitor = monitors_state.monitors[connector]
|
|
+
|
|
+ mode = parsed_args._current_sub_group.get("mode", None)
|
|
+ if not mode:
|
|
+ mode = monitor.preferred_mode
|
|
+
|
|
+ scales = mode.supported_scales
|
|
+ scales.sort(key=lambda scale: abs(scale - mode.preferred_scale))
|
|
+
|
|
+ return (repr(scale) for scale in scales)
|
|
+ except Exception:
|
|
+ return ()
|
|
+
|
|
+
|
|
+class NamedEnumCompleter(BaseCompleter):
|
|
+ def __init__(self, enum_type):
|
|
+ self.enum_type = enum_type
|
|
+
|
|
+ def __call__(self, **kwargs):
|
|
+ return (str(enum_value) for enum_value in self.enum_type)
|
|
+
|
|
+
|
|
+class LayoutModeCompleter(NamedEnumCompleter):
|
|
+ def __init__(self):
|
|
+ super().__init__(LayoutMode)
|
|
+
|
|
+
|
|
+class TransformCompleter(NamedEnumCompleter):
|
|
+ def __init__(self):
|
|
+ super().__init__(Transform)
|
|
+
|
|
+
|
|
if __name__ == "__main__":
|
|
parser = GdctlParser(
|
|
description="Display control utility",
|
|
@@ -1196,7 +1262,7 @@ if __name__ == "__main__":
|
|
choices=[str(layout_mode) for layout_mode in list(LayoutMode)],
|
|
type=str,
|
|
action=AppendToGlobal,
|
|
- )
|
|
+ ).completer = LayoutModeCompleter()
|
|
set_parser.add_argument(
|
|
"-L",
|
|
"--logical-monitor",
|
|
@@ -1216,8 +1282,9 @@ if __name__ == "__main__":
|
|
dest="monitors",
|
|
metavar="CONNECTOR",
|
|
action=SubGroupAction,
|
|
+ nargs=1,
|
|
help="Configure monitor",
|
|
- )
|
|
+ ).completer = MonitorCompleter()
|
|
monitor_parser = set_parser.add_argument_group(
|
|
"monitor",
|
|
"Monitor options (pass after --monitor)",
|
|
@@ -1229,7 +1296,7 @@ if __name__ == "__main__":
|
|
action=AppendToSubGroup,
|
|
help="Monitor mode",
|
|
type=str,
|
|
- )
|
|
+ ).completer = MonitorModeCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--primary",
|
|
"-p",
|
|
@@ -1245,7 +1312,7 @@ if __name__ == "__main__":
|
|
action=AppendToGroup,
|
|
help="Logical monitor scale",
|
|
type=float,
|
|
- )
|
|
+ ).completer = ScaleCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--transform",
|
|
"-t",
|
|
@@ -1253,7 +1320,7 @@ if __name__ == "__main__":
|
|
help="Apply viewport transform",
|
|
choices=[str(transform) for transform in list(Transform)],
|
|
type=str,
|
|
- )
|
|
+ ).completer = TransformCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--x",
|
|
"-x",
|
|
@@ -1274,28 +1341,38 @@ if __name__ == "__main__":
|
|
metavar="CONNECTOR",
|
|
help="Place right of other monitor",
|
|
type=str,
|
|
- )
|
|
+ ).completer = MonitorCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--left-of",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place left of other monitor",
|
|
type=str,
|
|
- )
|
|
+ ).completer = MonitorCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--above",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place above other monitor",
|
|
type=str,
|
|
- )
|
|
+ ).completer = MonitorCompleter()
|
|
logical_monitor_parser.add_argument(
|
|
"--below",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place below other monitor",
|
|
type=str,
|
|
- )
|
|
+ ).completer = MonitorCompleter()
|
|
+
|
|
+ for action in [
|
|
+ GroupAction,
|
|
+ SubGroupAction,
|
|
+ AppendToGroup,
|
|
+ AppendToSubGroup,
|
|
+ AppendToGlobal,
|
|
+ ]:
|
|
+ argcomplete.safe_actions.add(action)
|
|
+ argcomplete.autocomplete(parser, default_completer=SuppressCompleter)
|
|
|
|
args = parser.parse_args()
|
|
|
|
diff --git a/tools/meson.build b/tools/meson.build
|
|
index 763507c2e9..3aebe47643 100644
|
|
--- a/tools/meson.build
|
|
+++ b/tools/meson.build
|
|
@@ -3,5 +3,30 @@ install_data(
|
|
install_dir: bindir,
|
|
)
|
|
|
|
+if have_bash_completion
|
|
+ bash_completion = dependency('bash-completion', required: false)
|
|
+ if bash_completion.found()
|
|
+ bash_completion_dir = bash_completion.get_variable(pkgconfig: 'completionsdir')
|
|
+ else
|
|
+ bash_completion_dir = get_option('sysconfdir') / 'bash_completion.d'
|
|
+ endif
|
|
+
|
|
+ register_python_argcomplete = find_program('register-python-argcomplete')
|
|
+
|
|
+ custom_target(
|
|
+ 'gdctl-bash-completion',
|
|
+ output: 'gdctl',
|
|
+ command: [
|
|
+ register_python_argcomplete,
|
|
+ 'gdctl',
|
|
+ '--complete-arguments',
|
|
+ '-o nosort',
|
|
+ ],
|
|
+ capture: true,
|
|
+ install_dir: bash_completion_dir,
|
|
+ install: true,
|
|
+ )
|
|
+endif
|
|
+
|
|
gdctl = find_program('gdctl')
|
|
get_state_tool = find_program('get-state.py')
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 1e790c31b865cf8fb8c252bad5d32f1a5cdd8f37 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 29 Jan 2025 21:50:45 +0800
|
|
Subject: [PATCH 20/28] gdctl: Make LogicalMonitor a dataclass
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 4837d1ce64ce0b1a67b269dacc8d12d755f5067f)
|
|
---
|
|
tools/gdctl | 36 +++++++++++++-----------------------
|
|
1 file changed, 13 insertions(+), 23 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index caa670eb9c..db59299988 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -4,7 +4,7 @@ import argparse
|
|
import argcomplete
|
|
import sys
|
|
|
|
-from dataclasses import dataclass
|
|
+from dataclasses import dataclass, field
|
|
from gi.repository import GLib, Gio
|
|
from enum import Enum, Flag
|
|
from argcomplete.completers import BaseCompleter, SuppressCompleter
|
|
@@ -288,27 +288,18 @@ class Monitor:
|
|
self.display_name = self.properties.get("display-name", None)
|
|
|
|
|
|
+@dataclass
|
|
class LogicalMonitor:
|
|
- def __init__(
|
|
- self,
|
|
- monitors,
|
|
- scale,
|
|
- position=(0, 0),
|
|
- transform=Transform.NORMAL,
|
|
- is_primary=False,
|
|
- properties={},
|
|
- args=None,
|
|
- ):
|
|
- self.position = position
|
|
- self.scale = scale
|
|
- self.transform = transform
|
|
- self.is_primary = is_primary
|
|
- self.monitors = monitors
|
|
- self.properties = properties
|
|
- self.args = args
|
|
+ monitors: list[Monitor]
|
|
+ scale: float
|
|
+ position: tuple[int, int] | None = (0, 0)
|
|
+ transform: Transform = Transform.NORMAL
|
|
+ is_primary: bool = False
|
|
+ properties: dict = field(default_factory=dict)
|
|
+ args: dict | None = None
|
|
|
|
@classmethod
|
|
- def new_from_variant(cls, monitors_state, variant):
|
|
+ def from_variant(cls, monitors_state, variant):
|
|
position = (variant[0], variant[1])
|
|
scale = variant[2]
|
|
transform = Transform(variant[3])
|
|
@@ -682,9 +673,8 @@ def create_logical_monitor(monitors_state, layout_mode, logical_monitor_args):
|
|
monitors.append(monitor)
|
|
|
|
return LogicalMonitor(
|
|
- monitors_state,
|
|
- monitors,
|
|
- scale,
|
|
+ monitors=monitors,
|
|
+ scale=scale,
|
|
is_primary=is_primary,
|
|
transform=transform,
|
|
position=None,
|
|
@@ -833,7 +823,7 @@ class MonitorsState:
|
|
def init_logical_monitors(self, current_state):
|
|
self.logical_monitors = []
|
|
for variant in current_state[2]:
|
|
- logical_monitor = LogicalMonitor.new_from_variant(self, variant)
|
|
+ logical_monitor = LogicalMonitor.from_variant(self, variant)
|
|
self.logical_monitors.append(logical_monitor)
|
|
|
|
def create_current_config(self):
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From a5362e425df02fd16a822097196edeff1b9f3faa Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Wed, 29 Jan 2025 21:58:00 +0800
|
|
Subject: [PATCH 21/28] gdctl: Make Monitor a dataclass
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
|
|
(cherry picked from commit 4f218537cd69d71ab4d0cc5017db4bee02261dc8)
|
|
---
|
|
tools/gdctl | 51 ++++++++++++++++++++++++++++++++++++---------------
|
|
1 file changed, 36 insertions(+), 15 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index db59299988..bd8df818e4 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -260,32 +260,53 @@ class MonitorMode:
|
|
)
|
|
|
|
|
|
+@dataclass
|
|
class Monitor:
|
|
- def __init__(self, variant):
|
|
- self.init_from_variant(variant)
|
|
+ connector: str
|
|
+ vendor: str
|
|
+ product: str
|
|
+ display_name: str
|
|
+ serial: str
|
|
+ modes: list[MonitorMode]
|
|
+ properties: dict
|
|
+ current_mode: MonitorMode | None
|
|
+ preferred_mode: MonitorMode | None
|
|
|
|
- def init_from_variant(self, variant):
|
|
+ @classmethod
|
|
+ def from_variant(cls, variant):
|
|
spec = variant[0]
|
|
- self.connector = spec[0]
|
|
- self.vendor = spec[1] if spec[1] != "" else None
|
|
- self.product = spec[2] if spec[2] != "" else None
|
|
- self.serial = spec[3] if spec[3] != "" else None
|
|
- self.modes = [
|
|
+ connector = spec[0]
|
|
+ vendor = spec[1] if spec[1] != "" else None
|
|
+ product = spec[2] if spec[2] != "" else None
|
|
+ serial = spec[3] if spec[3] != "" else None
|
|
+ modes = [
|
|
MonitorMode.from_variant(mode_variant)
|
|
for mode_variant in variant[1]
|
|
]
|
|
- self.properties = translate_properties(variant[2])
|
|
+ properties = translate_properties(variant[2])
|
|
|
|
- self.current_mode = next(
|
|
- (mode for mode in self.modes if "is-current" in mode.properties),
|
|
+ current_mode = next(
|
|
+ (mode for mode in modes if "is-current" in mode.properties),
|
|
None,
|
|
)
|
|
- self.preferred_mode = next(
|
|
- (mode for mode in self.modes if "is-preferred" in mode.properties),
|
|
+ preferred_mode = next(
|
|
+ (mode for mode in modes if "is-preferred" in mode.properties),
|
|
None,
|
|
)
|
|
|
|
- self.display_name = self.properties.get("display-name", None)
|
|
+ display_name = properties.get("display-name", None)
|
|
+
|
|
+ return cls(
|
|
+ connector=connector,
|
|
+ vendor=vendor,
|
|
+ product=product,
|
|
+ serial=serial,
|
|
+ modes=modes,
|
|
+ properties=properties,
|
|
+ current_mode=current_mode,
|
|
+ preferred_mode=preferred_mode,
|
|
+ display_name=display_name,
|
|
+ )
|
|
|
|
|
|
@dataclass
|
|
@@ -817,7 +838,7 @@ class MonitorsState:
|
|
def init_monitors(self, current_state):
|
|
self.monitors = {}
|
|
for monitor_variant in current_state[1]:
|
|
- monitor = Monitor(monitor_variant)
|
|
+ monitor = Monitor.from_variant(monitor_variant)
|
|
self.monitors[monitor.connector] = monitor
|
|
|
|
def init_logical_monitors(self, current_state):
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From d44e477deca0cb86213a71e877aa2063ecb47e03 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Thu, 19 Dec 2024 00:44:40 +0100
|
|
Subject: [PATCH 22/28] gdctl: Add None friendly named enum helper to create
|
|
from string
|
|
|
|
If the string is None, don't create a named enum instance.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4192>
|
|
(cherry picked from commit 23ab763be03ff28fbbdae9fda79c24821385f93e)
|
|
---
|
|
tools/gdctl | 11 +++++++++++
|
|
1 file changed, 11 insertions(+)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index bd8df818e4..5bd5b32360 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -28,6 +28,17 @@ class NamedEnum(Enum):
|
|
if string == enum_string
|
|
)
|
|
|
|
+ @classmethod
|
|
+ def maybe_from_string(cls, string):
|
|
+ if string:
|
|
+ return next(
|
|
+ enum
|
|
+ for enum, enum_string in cls.enum_names()
|
|
+ if string == enum_string
|
|
+ )
|
|
+ else:
|
|
+ return None
|
|
+
|
|
|
|
class Transform(NamedEnum):
|
|
NORMAL = 0
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 578f10d981bd2a61b8028e9059d6ea03027a911e Mon Sep 17 00:00:00 2001
|
|
From: Sebastian Wick <sebastian.wick@redhat.com>
|
|
Date: Wed, 12 Feb 2025 15:13:44 +0100
|
|
Subject: [PATCH 23/28] gdctl: Fix typing and resulting handling of int|None
|
|
variables
|
|
|
|
Introduces two new NamedTuples to deal with dimensions and positions.
|
|
The position is special in that x and y can be None. This was previously
|
|
wrongly declared to be only int. This commit fixes instances mypy found
|
|
where None positions were not handled.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4267>
|
|
(cherry picked from commit 674ebecd00f4ebfcc5262ed9e07dc14a4bf75c24)
|
|
---
|
|
tools/gdctl | 111 +++++++++++++++++++++++++++++-----------------------
|
|
1 file changed, 62 insertions(+), 49 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 5bd5b32360..87bec9e8b4 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -5,7 +5,8 @@ import argcomplete
|
|
import sys
|
|
|
|
from dataclasses import dataclass, field
|
|
-from gi.repository import GLib, Gio
|
|
+from typing import NamedTuple, Any
|
|
+from gi.repository import GLib, Gio # type: ignore
|
|
from enum import Enum, Flag
|
|
from argcomplete.completers import BaseCompleter, SuppressCompleter
|
|
|
|
@@ -14,6 +15,16 @@ INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig"
|
|
|
|
|
|
+class Dimension(NamedTuple):
|
|
+ width: int
|
|
+ height: int
|
|
+
|
|
+
|
|
+class Position(NamedTuple):
|
|
+ x: int | None
|
|
+ y: int | None
|
|
+
|
|
+
|
|
class NamedEnum(Enum):
|
|
def __str__(self):
|
|
return next(
|
|
@@ -111,8 +122,7 @@ def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
|
|
|
|
if level >= 0:
|
|
indent = level
|
|
- buffer = f"{link:{padding}>{indent * 4}}──{data}"
|
|
- buffer = list(buffer)
|
|
+ buffer = list(f"{link:{padding}>{indent * 4}}──{data}")
|
|
for line in lines:
|
|
if line == level:
|
|
continue
|
|
@@ -120,11 +130,10 @@ def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
|
|
if line > 0:
|
|
index -= 1
|
|
buffer[index] = "│"
|
|
- buffer = "".join(buffer)
|
|
else:
|
|
- buffer = data
|
|
+ buffer = list(data)
|
|
|
|
- print(buffer)
|
|
+ print("".join(buffer))
|
|
|
|
if is_last and level in lines:
|
|
lines.remove(level)
|
|
@@ -168,7 +177,7 @@ def strip_dbus_error_prefix(message):
|
|
return message
|
|
|
|
|
|
-def transform_size(size, transform) -> tuple[int, int]:
|
|
+def transform_size(size: Dimension, transform) -> Dimension:
|
|
match transform:
|
|
case (
|
|
Transform.NORMAL
|
|
@@ -184,14 +193,14 @@ def transform_size(size, transform) -> tuple[int, int]:
|
|
| Transform.ROTATE_270_FLIPPED
|
|
):
|
|
width, height = size
|
|
- return (height, width)
|
|
+ return Dimension(height, width)
|
|
case _:
|
|
raise NotImplementedError
|
|
|
|
|
|
-def scale_size(size, scale) -> tuple[int, int]:
|
|
+def scale_size(size: Dimension, scale) -> Dimension:
|
|
width, height = size
|
|
- return (round(width / scale), round(height / scale))
|
|
+ return Dimension(round(width / scale), round(height / scale))
|
|
|
|
|
|
class DisplayConfig:
|
|
@@ -252,11 +261,10 @@ class DisplayConfig:
|
|
@dataclass
|
|
class MonitorMode:
|
|
name: str
|
|
- resolution: tuple[int, int]
|
|
+ resolution: Dimension
|
|
refresh_rate: float
|
|
preferred_scale: float
|
|
supported_scales: list[float]
|
|
- refresh_rate: float
|
|
properties: dict
|
|
|
|
@classmethod
|
|
@@ -324,11 +332,11 @@ class Monitor:
|
|
class LogicalMonitor:
|
|
monitors: list[Monitor]
|
|
scale: float
|
|
- position: tuple[int, int] | None = (0, 0)
|
|
+ position: Position = Position(0, 0)
|
|
transform: Transform = Transform.NORMAL
|
|
is_primary: bool = False
|
|
- properties: dict = field(default_factory=dict)
|
|
- args: dict | None = None
|
|
+ properties: dict[str, Any] = field(default_factory=dict)
|
|
+ args: dict[str, Any] = field(default_factory=dict)
|
|
|
|
@classmethod
|
|
def from_variant(cls, monitors_state, variant):
|
|
@@ -417,7 +425,7 @@ def place_right_of(
|
|
else:
|
|
y = None
|
|
|
|
- logical_monitor.position = (x, y)
|
|
+ logical_monitor.position = Position(x, y)
|
|
|
|
|
|
def place_left_of(
|
|
@@ -442,7 +450,7 @@ def place_left_of(
|
|
else:
|
|
y = None
|
|
|
|
- logical_monitor.position = (x, y)
|
|
+ logical_monitor.position = Position(x, y)
|
|
|
|
|
|
def place_below(
|
|
@@ -462,9 +470,9 @@ def place_below(
|
|
if set_x_position:
|
|
x, _ = connector_logical_monitor.position
|
|
else:
|
|
- x = logical_monitor.position[0]
|
|
+ x = logical_monitor.position.x
|
|
|
|
- logical_monitor.position = (x, y)
|
|
+ logical_monitor.position = Position(x, y)
|
|
|
|
|
|
def place_above(
|
|
@@ -487,9 +495,9 @@ def place_above(
|
|
if set_x_position:
|
|
x, _ = connector_logical_monitor.position
|
|
else:
|
|
- x = logical_monitor.position[0]
|
|
+ x = logical_monitor.position.x
|
|
|
|
- logical_monitor.position = (x, y)
|
|
+ logical_monitor.position = Position(x, y)
|
|
|
|
|
|
class PositionType(Flag):
|
|
@@ -519,13 +527,13 @@ def calculate_position(
|
|
|
|
set_y_position = vertical_args == 0
|
|
|
|
+ x = None
|
|
+ y = None
|
|
+
|
|
if "x" in logical_monitor.args:
|
|
x = int(logical_monitor.args["x"])
|
|
- if set_y_position:
|
|
- y = 0
|
|
- else:
|
|
- y = None
|
|
- logical_monitor.position = (x, y)
|
|
+ y = 0 if set_y_position else None
|
|
+ logical_monitor.position = Position(x, y)
|
|
position_types |= PositionType.ABSOLUTE_X
|
|
elif "right_of" in logical_monitor.args:
|
|
connector = logical_monitor.args["right_of"]
|
|
@@ -556,17 +564,14 @@ def calculate_position(
|
|
)
|
|
position_types |= PositionType.RELATIVE_X
|
|
else:
|
|
- logical_monitor.position = (0, 0)
|
|
+ logical_monitor.position = Position(0, 0)
|
|
|
|
set_x_position = horizontal_args == 0
|
|
|
|
if "y" in logical_monitor.args:
|
|
y = int(logical_monitor.args["y"])
|
|
- if set_x_position:
|
|
- x = 0
|
|
- else:
|
|
- x = logical_monitor.position[0]
|
|
- logical_monitor.position = (x, y)
|
|
+ x = 0 if set_x_position else logical_monitor.position.x
|
|
+ logical_monitor.position = Position(x, y)
|
|
position_types |= PositionType.ABSOLUTE_Y
|
|
elif "below" in logical_monitor.args:
|
|
connector = logical_monitor.args["below"]
|
|
@@ -596,17 +601,19 @@ def calculate_position(
|
|
x, y = logical_monitor.position
|
|
if not y:
|
|
y = 0
|
|
- logical_monitor.position = (x, y)
|
|
+ logical_monitor.position = Position(x, y)
|
|
|
|
- assert logical_monitor.position[0] is not None
|
|
- assert logical_monitor.position[1] is not None
|
|
+ assert logical_monitor.position.x is not None
|
|
+ assert logical_monitor.position.y is not None
|
|
|
|
return position_types
|
|
|
|
|
|
def align_horizontally(logical_monitors: list[LogicalMonitor]):
|
|
min_x = min(
|
|
- logical_monitor.position[0] for logical_monitor in logical_monitors
|
|
+ logical_monitor.position.x
|
|
+ for logical_monitor in logical_monitors
|
|
+ if logical_monitor.position.x is not None
|
|
)
|
|
|
|
dx = min_x
|
|
@@ -615,12 +622,16 @@ def align_horizontally(logical_monitors: list[LogicalMonitor]):
|
|
|
|
for logical_monitor in logical_monitors:
|
|
x, y = logical_monitor.position
|
|
- logical_monitor.position = (x - dx, y)
|
|
+ logical_monitor.position = Position(
|
|
+ x - dx if x is not None else None, y
|
|
+ )
|
|
|
|
|
|
def align_vertically(logical_monitors: list[LogicalMonitor]):
|
|
min_y = min(
|
|
- logical_monitor.position[1] for logical_monitor in logical_monitors
|
|
+ logical_monitor.position.y
|
|
+ for logical_monitor in logical_monitors
|
|
+ if logical_monitor.position.y is not None
|
|
)
|
|
|
|
dy = min_y
|
|
@@ -629,7 +640,9 @@ def align_vertically(logical_monitors: list[LogicalMonitor]):
|
|
|
|
for logical_monitor in logical_monitors:
|
|
x, y = logical_monitor.position
|
|
- logical_monitor.position = (x, y - dy)
|
|
+ logical_monitor.position = Position(
|
|
+ x, y - dy if y is not None else None
|
|
+ )
|
|
|
|
|
|
def calculate_positions(
|
|
@@ -1284,7 +1297,7 @@ if __name__ == "__main__":
|
|
choices=[str(layout_mode) for layout_mode in list(LayoutMode)],
|
|
type=str,
|
|
action=AppendToGlobal,
|
|
- ).completer = LayoutModeCompleter()
|
|
+ ).completer = LayoutModeCompleter() # type: ignore[attr-defined]
|
|
set_parser.add_argument(
|
|
"-L",
|
|
"--logical-monitor",
|
|
@@ -1306,7 +1319,7 @@ if __name__ == "__main__":
|
|
action=SubGroupAction,
|
|
nargs=1,
|
|
help="Configure monitor",
|
|
- ).completer = MonitorCompleter()
|
|
+ ).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
monitor_parser = set_parser.add_argument_group(
|
|
"monitor",
|
|
"Monitor options (pass after --monitor)",
|
|
@@ -1318,7 +1331,7 @@ if __name__ == "__main__":
|
|
action=AppendToSubGroup,
|
|
help="Monitor mode",
|
|
type=str,
|
|
- ).completer = MonitorModeCompleter()
|
|
+ ).completer = MonitorModeCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--primary",
|
|
"-p",
|
|
@@ -1334,7 +1347,7 @@ if __name__ == "__main__":
|
|
action=AppendToGroup,
|
|
help="Logical monitor scale",
|
|
type=float,
|
|
- ).completer = ScaleCompleter()
|
|
+ ).completer = ScaleCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--transform",
|
|
"-t",
|
|
@@ -1342,7 +1355,7 @@ if __name__ == "__main__":
|
|
help="Apply viewport transform",
|
|
choices=[str(transform) for transform in list(Transform)],
|
|
type=str,
|
|
- ).completer = TransformCompleter()
|
|
+ ).completer = TransformCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--x",
|
|
"-x",
|
|
@@ -1363,28 +1376,28 @@ if __name__ == "__main__":
|
|
metavar="CONNECTOR",
|
|
help="Place right of other monitor",
|
|
type=str,
|
|
- ).completer = MonitorCompleter()
|
|
+ ).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--left-of",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place left of other monitor",
|
|
type=str,
|
|
- ).completer = MonitorCompleter()
|
|
+ ).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--above",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place above other monitor",
|
|
type=str,
|
|
- ).completer = MonitorCompleter()
|
|
+ ).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
logical_monitor_parser.add_argument(
|
|
"--below",
|
|
action=AppendToGroup,
|
|
metavar="CONNECTOR",
|
|
help="Place below other monitor",
|
|
type=str,
|
|
- ).completer = MonitorCompleter()
|
|
+ ).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
|
|
for action in [
|
|
GroupAction,
|
|
@@ -1394,7 +1407,7 @@ if __name__ == "__main__":
|
|
AppendToGlobal,
|
|
]:
|
|
argcomplete.safe_actions.add(action)
|
|
- argcomplete.autocomplete(parser, default_completer=SuppressCompleter)
|
|
+ argcomplete.autocomplete(parser, default_completer=SuppressCompleter) # type: ignore[arg-type]
|
|
|
|
args = parser.parse_args()
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 50213e90a5ccf409593e61592c25ce7c05558a0f Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
|
|
Date: Sat, 15 Feb 2025 16:08:25 +0100
|
|
Subject: [PATCH 24/28] tools/gdctl: Make argcomplete optional
|
|
|
|
While having automatic completions is a very nice feature of gdctl (I
|
|
had suggested to use it too :)), it's not something that distros may
|
|
have by default and in particular it's not a package in main in ubuntu.
|
|
|
|
So, make the code less restrictive on completions, since completions is
|
|
not a core functionality of the tool and it can definitely work without
|
|
them.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4279>
|
|
(cherry picked from commit cfdc63df2ddff1bc5479757189bfc2772251cb40)
|
|
---
|
|
tools/gdctl | 35 +++++++++++++++++++++++------------
|
|
1 file changed, 23 insertions(+), 12 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 87bec9e8b4..ee81970bf3 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -1,14 +1,21 @@
|
|
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
-import argcomplete
|
|
import sys
|
|
|
|
from dataclasses import dataclass, field
|
|
-from typing import NamedTuple, Any
|
|
+from typing import NamedTuple, Any, Optional
|
|
+from types import ModuleType
|
|
from gi.repository import GLib, Gio # type: ignore
|
|
from enum import Enum, Flag
|
|
-from argcomplete.completers import BaseCompleter, SuppressCompleter
|
|
+
|
|
+argcomplete: Optional[ModuleType] = None
|
|
+BaseCompleter: Any
|
|
+try:
|
|
+ import argcomplete
|
|
+ from argcomplete.completers import BaseCompleter
|
|
+except ModuleNotFoundError:
|
|
+ BaseCompleter = object
|
|
|
|
NAME = "org.gnome.Mutter.DisplayConfig"
|
|
INTERFACE = "org.gnome.Mutter.DisplayConfig"
|
|
@@ -1399,15 +1406,19 @@ if __name__ == "__main__":
|
|
type=str,
|
|
).completer = MonitorCompleter() # type: ignore[attr-defined]
|
|
|
|
- for action in [
|
|
- GroupAction,
|
|
- SubGroupAction,
|
|
- AppendToGroup,
|
|
- AppendToSubGroup,
|
|
- AppendToGlobal,
|
|
- ]:
|
|
- argcomplete.safe_actions.add(action)
|
|
- argcomplete.autocomplete(parser, default_completer=SuppressCompleter) # type: ignore[arg-type]
|
|
+ if argcomplete:
|
|
+ for action in [
|
|
+ GroupAction,
|
|
+ SubGroupAction,
|
|
+ AppendToGroup,
|
|
+ AppendToSubGroup,
|
|
+ AppendToGlobal,
|
|
+ ]:
|
|
+ argcomplete.safe_actions.add(action)
|
|
+
|
|
+ argcomplete.autocomplete(
|
|
+ parser, default_completer=argcomplete.SuppressCompleter
|
|
+ ) # type: ignore[arg-type]
|
|
|
|
args = parser.parse_args()
|
|
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 08a54bd8108b7e4a602845c5669e1b278ac80b7c Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 25 Feb 2025 18:43:35 +0800
|
|
Subject: [PATCH 25/28] gdctl: Fix printing Position
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4302>
|
|
(cherry picked from commit 414156d0a186febe6fba713cce88cdb353b6d16c)
|
|
---
|
|
tools/gdctl | 3 +++
|
|
1 file changed, 3 insertions(+)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index ee81970bf3..42952a29fe 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -31,6 +31,9 @@ class Position(NamedTuple):
|
|
x: int | None
|
|
y: int | None
|
|
|
|
+ def __str__(self):
|
|
+ return f"({self.x}, {self.y})"
|
|
+
|
|
|
|
class NamedEnum(Enum):
|
|
def __str__(self):
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 40ff8aa1e0e1fc65cf3d2589e9d12e1f059ff41c Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 25 Feb 2025 18:45:40 +0800
|
|
Subject: [PATCH 26/28] gdctl: Add __str__(self) for Dimension too
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4302>
|
|
(cherry picked from commit c9c77684184061111a3db2bde822dece8c4f36f1)
|
|
---
|
|
tools/gdctl | 3 +++
|
|
1 file changed, 3 insertions(+)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 42952a29fe..34cd1f6750 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -26,6 +26,9 @@ class Dimension(NamedTuple):
|
|
width: int
|
|
height: int
|
|
|
|
+ def __str__(self):
|
|
+ return f"{self.width}x{self.height}"
|
|
+
|
|
|
|
class Position(NamedTuple):
|
|
x: int | None
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From fbe4cb34b129ab95eb3175330668aa9bf7b94054 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 25 Feb 2025 18:46:02 +0800
|
|
Subject: [PATCH 27/28] gdctl: Make MonitorMode.dimension an actual Dimension
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4302>
|
|
(cherry picked from commit 078ba5ea88d80381c82b423068ad52229da45d6f)
|
|
---
|
|
tools/gdctl | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index 34cd1f6750..ac070842cb 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -284,7 +284,7 @@ class MonitorMode:
|
|
def from_variant(cls, variant):
|
|
return cls(
|
|
name=variant[0],
|
|
- resolution=(variant[1], variant[2]),
|
|
+ resolution=Dimension(variant[1], variant[2]),
|
|
refresh_rate=variant[3],
|
|
preferred_scale=variant[4],
|
|
supported_scales=variant[5],
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 8e41ba56351dc7abcf9ed9cb951491d81117d7fc Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
|
|
Date: Tue, 25 Feb 2025 18:46:24 +0800
|
|
Subject: [PATCH 28/28] gdctl: Use Dimenison.__str__() to print mode dimension
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4302>
|
|
(cherry picked from commit 1d88f82d0d9b2b6f4ca0f3710ab1f2c1479830d6)
|
|
---
|
|
tools/gdctl | 3 +--
|
|
1 file changed, 1 insertion(+), 2 deletions(-)
|
|
|
|
diff --git a/tools/gdctl b/tools/gdctl
|
|
index ac070842cb..855cbc8eda 100755
|
|
--- a/tools/gdctl
|
|
+++ b/tools/gdctl
|
|
@@ -893,12 +893,11 @@ class MonitorsState:
|
|
if not show_properties:
|
|
return
|
|
|
|
- width, height = mode.resolution
|
|
print_data(
|
|
level=3,
|
|
is_last=False,
|
|
lines=lines,
|
|
- data=f"Dimension: {width}x{height}",
|
|
+ data=f"Dimension: {mode.resolution}",
|
|
)
|
|
print_data(
|
|
level=3,
|
|
--
|
|
2.49.0
|
|
|