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
105 changes: 104 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,10 @@
"""

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
from fractions import Fraction
import itertools

from checkbox_support.monitor_config import MonitorConfig

Expand Down Expand Up @@ -104,6 +106,107 @@
self._apply_monitors_config(state[0], extended_logical_monitors)
return configuration

def cycler(
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved
self,
res: bool = True,
max_res_amount: int = 5,
trans: bool = False,
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved
log: Callable[..., Any] = None,
action: Callable[..., Any] = None,
**kwargs
):
"""
Cycling change the monitors configurations automatically
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved

Args:
res: Cycling the resolution or not.
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved

max_res_amount: The supported resolution might be a lot
and this could limited test amount.
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved

trans: Cycling the transform or not.

log: For logging, the string is constructed by
[monitor name]_[resolution]_[transform]_.

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 trans else [0]

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

for mode in mode_combination:
for trans in trans_list:
logical_monitors = []
position_x = 0
m_list = list(mode)
uni_string = ""
for monitor in monitors:
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved
m = m_list.pop(0)
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 log:
log(uni_string, **kwargs)
if action:
action(uni_string, **kwargs)
if not res:
break
# change back to preferred monitor configuration
self.set_extended_mode()

def _resolution_filter(self, modes: List[Mode], max_res_amount: int):
hanhsuan marked this conversation as resolved.
Show resolved Hide resolved
new_modes = []
tmp_resolution = []
sort_modes = sorted(
modes, key=lambda m: int(m.resolution.split("x")[0]), reverse=True
)
for m in sort_modes:
width, height = [int(x) for x in m.resolution.split("x")]
aspect = Fraction(width, height)
if width < 675 or width / aspect < 530:
continue

Check warning on line 201 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#L201

Added line #L201 was not covered by tests
if m.resolution in tmp_resolution:
continue

Check warning on line 203 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#L203

Added line #L203 was not covered by tests
if len(new_modes) >= max_res_amount:
break

Check warning on line 205 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#L205

Added line #L205 was not covered by tests
new_modes.append(m)
tmp_resolution.append(m.resolution)
return new_modes

def _get_current_state(self) -> Tuple[str, Dict[str, List[Mode]]]:
"""
Using DBus signal 'GetCurrentState' to get the available monitors
Expand Down
177 changes: 177 additions & 0 deletions checkbox-support/checkbox_support/dbus/tests/test_gnome_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,180 @@ 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_cycler(self, mock_dbus_proxy):
"""
Test the cycler 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.cycler()

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_cycler_no_cycling(self, mock_dbus_proxy):
"""
Test the cycler could get the right monitors configuration
(without res and trans 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.cycler(
res=False, trans=False, log=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,
)
mock_callback.assert_called_with(
"eDP-1_1920x1200_normal_HDMI-1_2560x1440_normal_"
)
Loading