Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cycler method for cycling change the resolution and transform (New) #1576

Merged
merged 14 commits into from
Dec 3, 2024
Merged
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 @@
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))

Check warning on line 144 in checkbox-support/checkbox_support/dbus/gnome_monitor.py

View check run for this annotation

Codecov / codecov/patch

checkbox-support/checkbox_support/dbus/gnome_monitor.py#L144

Added line #L144 was not covered by tests
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),
p-gentili marked this conversation as resolved.
Show resolved Hide resolved
)
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)
Loading