Skip to content

Commit

Permalink
Add cycler method for cycling change the resolution and transform (Ne…
Browse files Browse the repository at this point in the history
…w) (#1576)

* Add cycler method for cycling change the resolution and transform to replace the old gnome_randr_cycle.py.

---------

Co-authored-by: Paolo Gentili <paolo.gentili@canonical.com>
  • Loading branch information
hanhsuan and p-gentili authored Dec 3, 2024
1 parent 0b0d7b0 commit 399280a
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 1 deletion.
81 changes: 80 additions & 1 deletion checkbox-support/checkbox_support/dbus/gnome_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
"""

from collections import namedtuple
from typing import Dict, List, Tuple, Set
from typing import Dict, List, Tuple, Set, Callable, Any
from gi.repository import GLib, Gio
import itertools

from checkbox_support.monitor_config import MonitorConfig

Expand Down Expand Up @@ -104,6 +105,84 @@ def set_extended_mode(self) -> Dict[str, str]:
self._apply_monitors_config(state[0], extended_logical_monitors)
return configuration

def cycle(
self,
resolution: bool = True,
transform: bool = False,
resolution_filter: Callable[[List[Mode]], List[Mode]] = None,
action: Callable[..., Any] = None,
**kwargs
):
"""
Automatically cycle through the supported monitor configurations.
Args:
resolution: Cycling the resolution or not.
transform: Cycling the transform or not.
resolution_filter: For filtering resolution then returning needed,
it will take List[Mode] as parameter and return
the same data type
action: For extra steps for each cycle,
the string is constructed by
[monitor name]_[resolution]_[transform]_.
Please note that the delay is needed inside this
callback to wait the monitors to response
"""
monitors = []
modes_list = []
# ["normal": 0, "left": 1, "inverted": 6, "right": 3]
trans_list = [0, 1, 6, 3] if transform else [0]

# for multiple monitors, we need to create resolution combination
state = self._get_current_state()
for monitor, modes in state[1].items():
monitors.append(monitor)
if resolution_filter:
modes_list.append(resolution_filter(modes))
else:
modes_list.append(modes)
mode_combination = list(itertools.product(*modes_list))

for mode in mode_combination:
for trans in trans_list:
logical_monitors = []
position_x = 0
uni_string = ""
for monitor, m in zip(monitors, mode):
uni_string += "{}_{}_{}_".format(
monitor,
m.resolution,
{
0: "normal",
1: "left",
3: "right",
6: "inverted",
}.get(trans),
)
logical_monitors.append(
(
position_x,
0,
1.0,
trans,
position_x == 0, # first monitor is primary
[(monitor, m.id, {})],
)
)
# left and right should convert x and y
xy = 1 if (trans == 1 or trans == 3) else 0
position_x += int(m.resolution.split("x")[xy])
self._apply_monitors_config(state[0], logical_monitors)
if action:
action(uni_string, **kwargs)
if not resolution:
break
# change back to preferred monitor configuration
self.set_extended_mode()

def _get_current_state(self) -> Tuple[str, Dict[str, List[Mode]]]:
"""
Using DBus signal 'GetCurrentState' to get the available monitors
Expand Down
183 changes: 183 additions & 0 deletions checkbox-support/checkbox_support/dbus/tests/test_gnome_monitor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import sys
import unittest
from unittest.mock import patch, Mock, MagicMock
Expand Down Expand Up @@ -230,3 +231,185 @@ def test_set_extended_mode(self, mock_dbus_proxy):
"HDMI-1": "2560x1440",
}
self.assertDictEqual(configuration, expected)

@patch("checkbox_support.dbus.gnome_monitor.Gio.DBusProxy")
def test_cycle(self, mock_dbus_proxy):
"""
Test the cycle could get the right monitors configuration
and send to ApplyMonitorsConfig.
"""

mock_proxy = Mock()
mock_dbus_proxy.new_for_bus_sync.return_value = mock_proxy

gnome_monitor = MonitorConfigGnome()
mock_proxy.call_sync.return_value = (
1,
[
(
("eDP-1", "LGD", "0x06b3", "0x00000000"),
[
(
"1920x1200@59.950",
1920,
1200,
59.950172424316406,
1.0,
[1.0, 2.0],
{
"is-current": GLib.Variant("b", True),
"is-preferred": GLib.Variant("b", True),
},
)
],
{
"is-builtin": GLib.Variant("b", True),
"display-name": GLib.Variant("s", "Built-in display"),
},
),
(
("HDMI-1", "LGD", "0x06b3", "0x00000000"),
[
(
"2560x1440@59.950",
2560,
1440,
59.950172424316406,
1.0,
[1.0, 2.0],
{
"is-current": GLib.Variant("b", True),
"is-preferred": GLib.Variant("b", True),
},
)
],
{
"is-builtin": GLib.Variant("b", False),
"display-name": GLib.Variant("s", "External Display"),
},
),
],
[],
{},
)
gnome_monitor.cycle()

logical_monitors = [
(0, 0, 1.0, 0, True, [("eDP-1", "1920x1200@59.950", {})]),
(1920, 0, 1.0, 0, False, [("HDMI-1", "2560x1440@59.950", {})]),
]

expected_logical_monitors = GLib.Variant(
"(uua(iiduba(ssa{sv}))a{sv})",
(
1,
1,
logical_monitors,
{},
),
)

mock_proxy.call_sync.assert_called_with(
method_name="ApplyMonitorsConfig",
parameters=expected_logical_monitors,
flags=Gio.DBusCallFlags.NONE,
timeout_msec=-1,
cancellable=None,
)

@patch("checkbox_support.dbus.gnome_monitor.Gio.DBusProxy")
def test_cycle_no_cycling(self, mock_dbus_proxy):
"""
Test the cycle could get the right monitors configuration
(without res and transform change) and send to ApplyMonitorsConfig.
"""

mock_proxy = Mock()
mock_dbus_proxy.new_for_bus_sync.return_value = mock_proxy

gnome_monitor = MonitorConfigGnome()
mock_proxy.call_sync.return_value = (
1,
[
(
("eDP-1", "LGD", "0x06b3", "0x00000000"),
[
(
"1920x1200@59.950",
1920,
1200,
59.950172424316406,
1.0,
[1.0, 2.0],
{
"is-current": GLib.Variant("b", True),
"is-preferred": GLib.Variant("b", True),
},
)
],
{
"is-builtin": GLib.Variant("b", True),
"display-name": GLib.Variant("s", "Built-in display"),
},
),
(
("HDMI-1", "LGD", "0x06b3", "0x00000000"),
[
(
"2560x1440@59.950",
2560,
1440,
59.950172424316406,
1.0,
[1.0, 2.0],
{
"is-current": GLib.Variant("b", True),
"is-preferred": GLib.Variant("b", True),
},
)
],
{
"is-builtin": GLib.Variant("b", False),
"display-name": GLib.Variant("s", "External Display"),
},
),
],
[],
{},
)
# mock callback
mock_callback = MagicMock()
gnome_monitor.cycle(
resolution=False,
transform=False,
resoultion_filter=mock_callback,
action=mock_callback,
)

logical_monitors = [
(0, 0, 1.0, 0, True, [("eDP-1", "1920x1200@59.950", {})]),
(1920, 0, 1.0, 0, False, [("HDMI-1", "2560x1440@59.950", {})]),
]

expected_logical_monitors = GLib.Variant(
"(uua(iiduba(ssa{sv}))a{sv})",
(
1,
1,
logical_monitors,
{},
),
)

mock_proxy.call_sync.assert_called_with(
method_name="ApplyMonitorsConfig",
parameters=expected_logical_monitors,
flags=Gio.DBusCallFlags.NONE,
timeout_msec=-1,
cancellable=None,
)
argument_string = mock_callback.call_args[0][0]
p1 = "HDMI-1_2560x1440_normal_"
p2 = "eDP-1_1920x1200_normal_"
pattern = re.compile("{}{}|{}{}".format(p1, p2, p2, p1))
assert pattern.match(argument_string)

0 comments on commit 399280a

Please sign in to comment.